This commit is contained in:
sidibe
2025-11-19 12:20:37 +00:00
commit 1972c8ff90
86 changed files with 3373 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package com.pmu.betengine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BetEngineApplication {
public static void main(String[] args) {
SpringApplication.run(BetEngineApplication.class, args);
}
}

View File

@@ -0,0 +1,31 @@
package com.pmu.betengine.api;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "api_keys")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApiKey {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 128)
private String keyValue;
@Column(nullable = false)
private Boolean active;
@Column(nullable = false)
private LocalDateTime createdAt;
}

View File

@@ -0,0 +1,30 @@
package com.pmu.betengine.api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/admin/api-keys")
@RequiredArgsConstructor
public class ApiKeyController {
private final ApiKeyService apiKeyService;
@PostMapping("/create")
public ApiKey createApiKey() {
String generatedKey = UUID.randomUUID().toString().replace("-", "");
return apiKeyService.createKey(generatedKey);
}
// @PutMapping("/deactivate/{id}")
public String deactivateApiKey(@PathVariable Long id) {
apiKeyService.deactivateKey(id);
return "API Key désactivée avec succès";
}
}

View File

@@ -0,0 +1,34 @@
package com.pmu.betengine.api;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class ApiKeyFilter extends OncePerRequestFilter {
private final ApiKeyService apiKeyService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String requestApiKey = request.getHeader("x-api-key");
/* if (requestApiKey == null || !apiKeyService.isValid(requestApiKey)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid or missing API Key");
return;
}*/
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,10 @@
package com.pmu.betengine.api;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ApiKeyRepository extends JpaRepository<ApiKey, Long> {
Optional<ApiKey> findByKeyValueAndActiveTrue(String keyValue);
}

View File

@@ -0,0 +1,32 @@
package com.pmu.betengine.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ApiKeyService {
private final ApiKeyRepository apiKeyRepository;
public boolean isValid(String key) {
return apiKeyRepository.findByKeyValueAndActiveTrue(key).isPresent();
}
public ApiKey createKey(String key) {
ApiKey apiKey = ApiKey.builder()
.keyValue(key)
.active(true)
.createdAt(java.time.LocalDateTime.now())
.build();
return apiKeyRepository.save(apiKey);
}
public void deactivateKey(Long id) {
apiKeyRepository.findById(id).ifPresent(apiKey -> {
apiKey.setActive(false);
apiKeyRepository.save(apiKey);
});
}
}

View File

@@ -0,0 +1,29 @@
package com.pmu.betengine.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // Tous les endpoints
.allowedOrigins("*"
// "http://localhost:3000",
// "http://frontend-domain.com",
// "https://frontend-domain.com"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
// .allowCredentials(true)
.maxAge(3600); // Cache la réponse preflight pendant 1 heure
}
};
}
}

View File

@@ -0,0 +1,23 @@
package com.pmu.betengine.config;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setSkipNullEnabled(true)
.setAmbiguityIgnored(true)
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE);
return modelMapper;
}
}

View File

@@ -0,0 +1,33 @@
package com.pmu.betengine.config;
import com.pmu.betengine.api.ApiKeyFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final ApiKeyFilter apiKeyFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").permitAll() // tu peux protéger plus tard
.requestMatchers("/**").permitAll() // tu peux protéger plus tard
.anyRequest().authenticated()
)
.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}

View File

@@ -0,0 +1,29 @@
package com.pmu.betengine.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI gymManagementOpenAPI() {
return new OpenAPI()
.info(new Info().title("PMU PLR Management API")
.description("API pour la Gestion des Paris PLR")
.version("v1.0")
.contact(new Contact()
.name("PMU")
.email("sidibe.abdoulkarim@pmumali.ml")))
.externalDocs(new ExternalDocumentation()
.description("Documentation Wiki")
.url("https://www.pmumali.ml"));
}
}

View File

@@ -0,0 +1,76 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.dto.AgentFamilyMemberDTO;
import com.pmu.betengine.model.dto.AgentFamilyMemberRequestDTO;
import com.pmu.betengine.service.AgentFamilyMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("/api/v1/agent-family-members")
@CrossOrigin(origins = "*")
@Tag(name = "Gestion des Membres de famille", description = "Endpoints relatifs à l'objet AgentFamilyMember")
public class AgentFamilyMemberController {
private final AgentFamilyMemberService agentFamilyMemberService;
/* public AgentFamilyMemberController(AgentFamilyMemberService agentFamilyMemberService) {
this.agentFamilyMemberService = agentFamilyMemberService;
}*/
@GetMapping
public ResponseEntity<List<AgentFamilyMemberDTO>> getAllFamilyMembers() {
return ResponseEntity.ok(agentFamilyMemberService.getAllFamilyMembers());
}
@GetMapping("/{id}")
public ResponseEntity<AgentFamilyMemberDTO> getFamilyMemberById(@PathVariable Long id) {
return ResponseEntity.ok(agentFamilyMemberService.getFamilyMemberById(id));
}
@PostMapping
@Operation(summary = "Ajouter un AgentFamilyMember dans la base")
public ResponseEntity<AgentFamilyMemberDTO> createFamilyMember(@Valid @RequestBody AgentFamilyMemberRequestDTO requestDTO) {
return new ResponseEntity<>(agentFamilyMemberService.createFamilyMember(requestDTO), HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<AgentFamilyMemberDTO> updateFamilyMember(@PathVariable Long id, @Valid @RequestBody AgentFamilyMemberRequestDTO requestDTO) {
return ResponseEntity.ok(agentFamilyMemberService.updateFamilyMember(id, requestDTO));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteFamilyMember(@PathVariable Long id) {
agentFamilyMemberService.deleteFamilyMember(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/statut/{statut}")
public ResponseEntity<List<AgentFamilyMemberDTO>> getFamilyMembersByStatut(@PathVariable String statut) {
return ResponseEntity.ok(agentFamilyMemberService.getFamilyMembersByStatut(statut));
}
@GetMapping("/sexe/{sexe}")
public ResponseEntity<List<AgentFamilyMemberDTO>> getFamilyMembersBySexe(@PathVariable String sexe) {
return ResponseEntity.ok(agentFamilyMemberService.getFamilyMembersBySexe(sexe));
}
@GetMapping("/search")
public ResponseEntity<List<AgentFamilyMemberDTO>> searchFamilyMembers(@RequestParam String keyword) {
return ResponseEntity.ok(agentFamilyMemberService.searchFamilyMembers(keyword));
}
@GetMapping("/nom/{nom}")
public ResponseEntity<List<AgentFamilyMemberDTO>> getFamilyMembersByNom(@PathVariable String nom) {
return ResponseEntity.ok(agentFamilyMemberService.getFamilyMembersByNom(nom));
}
}

View File

@@ -0,0 +1,39 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.Cheval;
import com.pmu.betengine.service.ChevalService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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/v1/cheval")
@Tag(name = "Gestion des Chevaux", description = "Endpoints relatifs à l'objet cheval")
public class ChevalController {
@Autowired
private ChevalService chevalService;
@PostMapping
@Operation(summary = "Ajouter un cheval dans la base")
public ResponseEntity<Cheval> ajouterCheval(@RequestBody Cheval cheval) {
return ResponseEntity.ok(chevalService.ajouterCheval(cheval));
}
@GetMapping("/course/{courseId}")
@Operation(summary = "Affiche les chevaux d'une Course donnée")
public ResponseEntity<List<Cheval>> obtenirChevauxParCourse(@PathVariable Long courseId) {
return ResponseEntity.ok(chevalService.obtenirChevauxParCourse(courseId));
}
@GetMapping("/ecurie/{nomEcurie}")
@Operation(summary = "Affiche les chevaux d'une écurie donnée")
public ResponseEntity<List<Cheval>> obtenirChevauxParEcurie(@PathVariable String nomEcurie) {
return ResponseEntity.ok(chevalService.obtenirChevauxParEcurie(nomEcurie));
}
}

View File

@@ -0,0 +1,91 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.Cheval;
import com.pmu.betengine.model.Course;
import com.pmu.betengine.model.dto.NewCourse;
import com.pmu.betengine.model.statut.StatutCourse;
import com.pmu.betengine.service.CourseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import static com.pmu.betengine.util.ChevalUtil.fromIntegerList;
@RestController
@RequestMapping("/api/v1/course")
@Tag(name = "Gestion des Courses", description = "Endpoints relatifs à l'objet course")
public class CourseController {
@Autowired
private CourseService courseService;
@PostMapping
@Operation(summary = "Création d'une Nouvelle Course")
@ApiResponse(responseCode = "201", description = "Course Enregistrée")
public ResponseEntity<String> creerCourse(@RequestBody NewCourse course) {
Course courseNew =null;
try {
courseNew = courseService.creerCourse(fromDto(course));
}catch (Exception e){
e.printStackTrace();
}
if(courseNew!=null && courseNew.getId()!=null)
return new ResponseEntity<>("Course ID = "+courseNew.getId(), HttpStatus.CREATED);
else {
return new ResponseEntity<>("Erreur de Création de la Course", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// @GetMapping
public ResponseEntity<List<Course>> obtenirToutesCourses() {
return ResponseEntity.ok(courseService.obtenirToutesCourses());
}
@GetMapping("/{id}")
@Operation(summary = "Obtenir une Course à travers son ID")
public ResponseEntity<Course> obtenirCourseParId(@PathVariable Long id) {
Course course = courseService.obtenirCourseParId(id);
return course != null ? ResponseEntity.ok(course) : ResponseEntity.notFound().build();
}
@GetMapping("/terminees")
@Operation(summary = "Afficher toutes les courses terminées")
public ResponseEntity<List<Course>> obtenirCoursesTerminees() {
return ResponseEntity.ok(courseService.obtenirCoursesTerminees());
}
@GetMapping("/avenir")
@Operation(summary = "Afficher les Courses à venir")
public ResponseEntity<List<Course>> obtenirCoursesAVenir() {
return ResponseEntity.ok(courseService.obtenirCoursesAVenir());
}
private Course fromDto(NewCourse dto){
return Course.builder()
.heureCourse(dto.getHeureCourse())
.numero(dto.getNumero())
.aDeadHeat(false)
.estAnnulee(false)
.estTerminee(false)
.lieu(dto.getLieu())
.reunion(dto.getReunion())
.statut(StatutCourse.CREATED)
// .nombreChevauxInscrits(dto.getChevaux() != null ? dto.getChevaux().size() : 0)
// .chevaux(fromIntegerList(dto.getChevaux()))
.build();
}
}

View File

@@ -0,0 +1,70 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.dto.HippodromeDTO;
import com.pmu.betengine.model.dto.HippodromeRequestDTO;
import com.pmu.betengine.service.HippodromeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/hippodromes")
@CrossOrigin(origins = "*")
@Tag(name = "Gestion des Hippodromes ", description = "Endpoints relatifs à l'objet Hippodrome")
public class HippodromeController {
private final HippodromeService hippodromeService;
public HippodromeController(HippodromeService hippodromeService) {
this.hippodromeService = hippodromeService;
}
@GetMapping
public ResponseEntity<List<HippodromeDTO>> getAllHippodromes() {
return ResponseEntity.ok(hippodromeService.getAllHippodromes());
}
@GetMapping("/{id}")
public ResponseEntity<HippodromeDTO> getHippodromeById(@PathVariable Long id) {
return ResponseEntity.ok(hippodromeService.getHippodromeById(id));
}
@PostMapping
@Operation(summary = "Enregistrer Un Hippodrome",
description = "Méthode permettant d'enregistrer l'Hippodrome")
public ResponseEntity<HippodromeDTO> createHippodrome(@Valid @RequestBody HippodromeRequestDTO requestDTO) {
return new ResponseEntity<>(hippodromeService.createHippodrome(requestDTO), HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<HippodromeDTO> updateHippodrome(@PathVariable Long id, @Valid @RequestBody HippodromeRequestDTO requestDTO) {
return ResponseEntity.ok(hippodromeService.updateHippodrome(id, requestDTO));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteHippodrome(@PathVariable Long id) {
hippodromeService.deleteHippodrome(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/ville/{ville}")
public ResponseEntity<List<HippodromeDTO>> getHippodromesByVille(@PathVariable String ville) {
return ResponseEntity.ok(hippodromeService.getHippodromesByVille(ville));
}
@GetMapping("/actifs")
public ResponseEntity<List<HippodromeDTO>> getHippodromesActifs() {
return ResponseEntity.ok(hippodromeService.getHippodromesActifs());
}
@GetMapping("/reunion/{reunionId}")
public ResponseEntity<HippodromeDTO> getHippodromeByReunion(@PathVariable Long reunionId) {
return ResponseEntity.ok(hippodromeService.getHippodromeByReunion(reunionId));
}
}

View File

@@ -0,0 +1,113 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.Pari;
import com.pmu.betengine.model.dto.NewPari;
import com.pmu.betengine.model.dto.updatePari;
import com.pmu.betengine.model.statut.StatutParis;
import com.pmu.betengine.model.type.TypeFormule;
import com.pmu.betengine.service.CourseService;
import com.pmu.betengine.service.PariService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.pmu.betengine.util.ChevalUtil.fromIntegerList;
import static com.pmu.betengine.util.ChevalUtil.numeroToCheval;
@RestController
@RequestMapping("/api/v1/pari")
@Tag(name = "Gestion des Pari (Bet)", description = "Endpoints relatifs à l'objet pari")
public class PariController {
@Autowired
private PariService pariService;
@Autowired
private CourseService courseService;
@PostMapping
@Operation(summary = "Placer un Pari (Bet)",
description = "Permet d'enregistrer tous les types de Pari (Bet) avec toutes les Formules possibles")
@ApiResponse(responseCode = "201", description = "Nouveau Pari Enregistré")
public ResponseEntity<Pari> placerPari(@RequestBody NewPari pari) {
try {
return new ResponseEntity<>(pariService.placerPari(fromNewPariDto(pari)), HttpStatus.CREATED);
}catch (Exception e){
e.printStackTrace();
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/course/{courseId}")
@Operation(summary = "Affiche tous les Pari (Bet) d'une Course")
@ApiResponse(responseCode = "200", description = "Paris Trouvés")
public ResponseEntity<List<Pari>> obtenirParisParCourse(@PathVariable Long courseId) {
return ResponseEntity.ok(pariService.obtenirParisParCourse(courseId));
}
@PutMapping("/{ticket}")
@Operation(summary = "Mettre à jour un Pari Existant",description = "Cette opération concerne le status (), payé ou remboursé")
@ApiResponse(responseCode = "200", description = "Pari Modifié avec succès")
@ApiResponse(responseCode = "404", description = "Pari Non Trouvé")
public ResponseEntity<String> updateProduct(@PathVariable String ticket, @RequestBody updatePari updatePari) {
// Find the product by ID
Pari pari = pariService.findByTicket(ticket);
if (pari != null) {
pari.setStatut(updatePari.getStatus());
pari.setEstRembourse(updatePari.isEstRembourse());
pari.setEstPaye(updatePari.isEstPaye());
Pari pariUpdate = pariService.update(pari);
return ResponseEntity.ok(ticket); // Return the updated product
} else {
return ResponseEntity.status(404).body(ticket);
}
}
// @GetMapping("/course/{courseId}/type/{typePari}")
public ResponseEntity<List<Pari>> obtenirParisParCourseEtType(
@PathVariable Long courseId, @PathVariable TypeFormule typeFormule) {
return ResponseEntity.ok(pariService.obtenirParisParCourseEtType(courseId, typeFormule));
}
// @GetMapping("/cheval/{chevalId}")
public ResponseEntity<List<Pari>> obtenirParisParCheval(@PathVariable Long chevalId) {
return ResponseEntity.ok(pariService.obtenirParisParCheval(chevalId));
}
private Pari fromNewPariDto(NewPari newPari){
return Pari.builder()
.datePari(newPari.getDatePari())
.course(courseService.obtenirCourseParId(newPari.getCourseId()))
.cheval(numeroToCheval(newPari.getCheval()))
.cheval1(numeroToCheval(newPari.getCheval1()))
.cheval2(numeroToCheval(newPari.getCheval2()))
.cheval3(numeroToCheval(newPari.getCheval3()))
.chevauxOrdre(newPari.getChevauxOrdre())
.chevauxSelectionnes(fromIntegerList(newPari.getChevauxSelectionnes()))
.numeroTicket(newPari.getNumeroTicket())
.nomParieur(newPari.getNomParieur())
.idParieur(newPari.getIdParieur())
.statut(StatutParis.EN_ATTENTE)
.mise(newPari.getMise())
.premier(numeroToCheval(newPari.getPremier()))
.deuxieme(numeroToCheval(newPari.getDeuxieme()))
.troisieme(numeroToCheval(newPari.getTroisieme()))
.typeFormule(newPari.getTypeFormule())
.typePari(newPari.getTypePari())
.estRembourse(false)
.estPaye(false)
.typeMulti(newPari.getTypeMulti())
.ordrePredit(newPari.getOrdrePredit())
.validationOrdreExact(newPari.getValidationOrdreExact())
.build();
}
}

View File

@@ -0,0 +1,54 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.Resultat;
import com.pmu.betengine.model.dto.NewResultat;
import com.pmu.betengine.model.dto.ResultatDto;
import com.pmu.betengine.service.CourseService;
import com.pmu.betengine.service.ResultatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.pmu.betengine.util.ChevalUtil.fromIntegerList;
@RestController
@RequestMapping("/api/v1/resultat")
@Tag(name = "Gestion des Resultats des Courses", description = "Endpoints relatifs à l'objet resultat")
public class ResultatController {
@Autowired
private ResultatService resultatCourseService;
@Autowired
private CourseService courseService;
@PostMapping
@Operation(summary = "Enregistrer le Resultat d'une course",
description = "Méthode permettant d'enregistrer le résultat d'une course")
public ResponseEntity<?> enregistrerResultat(@RequestBody NewResultat resultatDto) {
try {
resultatCourseService.save(fromNewResultat(resultatDto));
return ResponseEntity.ok().build();
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
private Resultat fromNewResultat(NewResultat resultat){
return Resultat.builder()
.course(courseService.obtenirCourseParId(resultat.getIdCourse().longValue()))
.aDeadHeat(resultat.isADeadHeat())
.premiers(fromIntegerList(resultat.getChevauxPremiers()))
.seconds(fromIntegerList(resultat.getChevauxDeuxiemes()))
.troisiemes(fromIntegerList(resultat.getChevauxTroisiemes()))
.quatriemes(fromIntegerList(resultat.getChevauxQuatriemes()))
.cinquiemes(fromIntegerList(resultat.getChevauxCinquiemes()))
.ordreArrivee(fromIntegerList(resultat.getOrdreArrivee()))
.build();
}
}

View File

@@ -0,0 +1,54 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.dto.ReunionDTO;
import com.pmu.betengine.model.dto.ReunionRequestDTO;
import com.pmu.betengine.service.ReunionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/reunions")
@Tag(name = "Gestion des Réunions des Courses", description = "Endpoints relatifs à l'objet Reunion")
@CrossOrigin(origins = "*")
public class ReunionController {
private final ReunionService reunionService;
public ReunionController(ReunionService reunionService) {
this.reunionService = reunionService;
}
@GetMapping
public ResponseEntity<List<ReunionDTO>> getAllReunions() {
return ResponseEntity.ok(reunionService.getAllReunions());
}
@GetMapping("/{id}")
public ResponseEntity<ReunionDTO> getReunionById(@PathVariable Long id) {
return ResponseEntity.ok(reunionService.getReunionById(id));
}
@PostMapping
@Operation(summary = "Enregistrer Une réunion",
description = "Méthode permettant d'enregistrer la réunion")
public ResponseEntity<ReunionDTO> createReunion(@Valid @RequestBody ReunionRequestDTO requestDTO) {
return new ResponseEntity<>(reunionService.createReunion(requestDTO), HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<ReunionDTO> updateReunion(@PathVariable Long id, @Valid @RequestBody ReunionRequestDTO requestDTO) {
return ResponseEntity.ok(reunionService.updateReunion(id, requestDTO));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteReunion(@PathVariable Long id) {
reunionService.deleteReunion(id);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,124 @@
package com.pmu.betengine.controller;
import com.pmu.betengine.model.dto.TPEDTO;
import com.pmu.betengine.model.dto.TPERequestDTO;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.service.TPEService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/tpes")
@CrossOrigin(origins = "*")
@Tag(name = "Gestion des TPE", description = "Endpoints relatifs à l'objet TPE")
public class TPEController {
private final TPEService tpeService;
public TPEController(TPEService tpeService) {
this.tpeService = tpeService;
}
@GetMapping
public ResponseEntity<List<TPEDTO>> getAllTPEs() {
return ResponseEntity.ok(tpeService.getAllTPEs());
}
@GetMapping("/{id}")
public ResponseEntity<TPEDTO> getTPEById(@PathVariable Long id) {
return ResponseEntity.ok(tpeService.getTPEById(id));
}
@PostMapping
@Operation(summary = "Enregistrer Un TPE",
description = "Méthode permettant d'enregistrer un TPE")
public ResponseEntity<TPEDTO> createTPE(@Valid @RequestBody TPERequestDTO requestDTO) {
return new ResponseEntity<>(tpeService.createTPE(requestDTO), HttpStatus.CREATED);
}
@PutMapping("/{id}")
@Operation(summary = "Modifier Un TPE",
description = "Méthode permettant de modifier un TPE")
public ResponseEntity<TPEDTO> updateTPE(@PathVariable Long id, @Valid @RequestBody TPERequestDTO requestDTO) {
return ResponseEntity.ok(tpeService.updateTPE(id, requestDTO));
}
@DeleteMapping("/{id}")
@Operation(summary = "Supprimer Un TPE",
description = "Méthode permettant de supprimer un TPE")
public ResponseEntity<Void> deleteTPE(@PathVariable Long id) {
tpeService.deleteTPE(id);
return ResponseEntity.noContent().build();
}
@PatchMapping("/{id}/statut")
@Operation(summary = "Modifier le Statut d'un TPE",
description = "Méthode permettant de modifier le statut d'un TPE")
public ResponseEntity<TPEDTO> updateStatut(@PathVariable Long id, @RequestBody Map<String, String> request) {
String statutStr = request.get("statut");
try {
StatutTPE statut = StatutTPE.valueOf(statutStr.toUpperCase());
return ResponseEntity.ok(tpeService.updateStatut(id, statut));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Statut invalide: " + statutStr);
}
}
@PatchMapping("/{id}/assigner")
@Operation(summary = "Assigner Un TPE",
description = "Méthode permettant d'd'assigner un TPE")
public ResponseEntity<TPEDTO> assignerTPE(@PathVariable Long id) {
return ResponseEntity.ok(tpeService.assignerTPE(id));
}
@PatchMapping("/{id}/liberer")
@Operation(summary = "Libérer Un TPE",
description = "Méthode permettant de libérer un TPE")
public ResponseEntity<TPEDTO> libererTPE(@PathVariable Long id) {
return ResponseEntity.ok(tpeService.libererTPE(id));
}
@GetMapping("/statut/{statut}")
@Operation(summary = "Lister les TPE par statut",
description = "Méthode permettant d'obtenir les TPE du statut en parametre")
public ResponseEntity<List<TPEDTO>> getTPEsByStatut(@PathVariable StatutTPE statut) {
return ResponseEntity.ok(tpeService.getTPEsByStatut(statut));
}
@GetMapping("/disponibles")
@Operation(summary = "Lister les TPE Disponibles",
description = "Méthode permettant de lister les TPE disponibles")
public ResponseEntity<List<TPEDTO>> getTPEsDisponibles() {
return ResponseEntity.ok(tpeService.getTPEsDisponibles());
}
@GetMapping("/search")
public ResponseEntity<List<TPEDTO>> searchTPEs(@RequestParam String q) {
return ResponseEntity.ok(tpeService.searchTPEs(q));
}
@GetMapping("/stats/count-by-statut")
public ResponseEntity<Map<StatutTPE, Long>> getCountByStatut() {
Map<StatutTPE, Long> stats = Map.of(
StatutTPE.DISPONIBLE, tpeService.countTPEsByStatut(StatutTPE.DISPONIBLE),
StatutTPE.AFFECTE, tpeService.countTPEsByStatut(StatutTPE.AFFECTE),
StatutTPE.EN_PANNE, tpeService.countTPEsByStatut(StatutTPE.EN_PANNE),
StatutTPE.EN_MAINTENANCE, tpeService.countTPEsByStatut(StatutTPE.EN_MAINTENANCE),
StatutTPE.HORS_SERVICE, tpeService.countTPEsByStatut(StatutTPE.HORS_SERVICE),
StatutTPE.VOLE, tpeService.countTPEsByStatut(StatutTPE.VOLE)
);
return ResponseEntity.ok(stats);
}
@GetMapping("/stats/assignes")
public ResponseEntity<Long> getCountAssignes() {
return ResponseEntity.ok(tpeService.countTPEsAssignes());
}
}

View File

@@ -0,0 +1,47 @@
package com.pmu.betengine.exception;
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
ex.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@Data
public static class ErrorResponse {
private int status;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(int status, String message, LocalDateTime timestamp) {
this.status = status;
this.message = message;
this.timestamp = timestamp;
}
}
}

View File

@@ -0,0 +1,85 @@
package com.pmu.betengine.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.statut.StatutAgent;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "agent")
public class Agent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private String profile;
private String principalCode;
private String caisseProfile;
private StatutAgent statut;
private String zone;
private String kiosk;
private String fonction;
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date doit être au format dd/MM/yyyy")
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
private LocalDate dateEmbauche;
@NotBlank(message = "Le nom est obligatoire")
private String nom;
private String prenom;
private String autresNoms;
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date de naissance doit être au format dd/MM/yyyy")
@Column(name = "date_naissance", nullable = false)
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
private LocalDate dateNaissance;
private String lieuNaissance;
private String ville;
private String adresse;
private Boolean autoriserAides;
private String phone;
private String pin;
private Double limiteInferieure;
private Double limiteSuperieure;
private Double limiteParTransaction;
private Double limiteMinAirtime;
private Double limiteMaxAirtime;
private Integer maxPeripheriques;
private String limitId;
// Légales
private String nationalite;
private String cni;
private String cniDelivreeLe;
private String cniDelivreeA;
private String residence;
private String autreAdresse1;
private String statutMarital;
private String epoux;
private String autreTelephone;
// Situation familiale
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "agent_famille",
joinColumns = @JoinColumn(name = "agent_id"),
inverseJoinColumns = @JoinColumn(name = "famille_id"))
private List<AgentFamilyMember> famille;
// TPE assignés
private String [] assignedTpeIds;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
private String createdBy;
}

View File

@@ -0,0 +1,36 @@
package com.pmu.betengine.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "agent_family_member")
public class AgentFamilyMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Le nom est obligatoire")
public String nom;
public String statut;
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date de naissance doit être au format dd/MM/yyyy")
@Column(name = "date_naissance", nullable = false)
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
public LocalDate dateNaissance;
@NotBlank(message = "Le sexe est obligatoire")
@Pattern(regexp = "^(M|F)$", message = "Le sexe doit être M (Masculin) ou F (Féminin)")
@Column(nullable = false, length = 1)
public String sexe;
}

View File

@@ -0,0 +1,40 @@
package com.pmu.betengine.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "agent_limit")
public class AgentLimit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public String code;
public String configCode;
public String nom;
public boolean isDefault;
public boolean actif;
// Bet limits (Double for nullable number fields)
public Double betMin;
public Double betMax;
public Double maxBet;
public Double maxDisburseBet;
// Airtime
public Double airtimeMin;
public Double airtimeMax;
@CreationTimestamp
public LocalDateTime createdAt;
public String createdBy;
}

View File

@@ -0,0 +1,27 @@
package com.pmu.betengine.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "cheval")
public class Cheval {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private int numero;
@Column(name = "non_partant")
private boolean nonPartant;
private String nomEcurie;
}

View File

@@ -0,0 +1,56 @@
package com.pmu.betengine.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.pmu.betengine.model.statut.StatutCourse;
import jakarta.persistence.*;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String numero;
@OneToOne
private Reunion reunion;
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date doit être au format dd/MM/yyyy HH:mm:ss")
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", shape = JsonFormat.Shape.STRING)
@Column(name = "heure_course")
private LocalDateTime heureCourse;
private String lieu;
private int nombreChevauxInscrits;
private boolean estTerminee;
private boolean estAnnulee;
private boolean aDeadHeat;
@Enumerated(EnumType.STRING)
private StatutCourse statut;
@OneToOne(mappedBy = "course")
private Resultat resultat;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "course_chevaux",
joinColumns = @JoinColumn(name = "course_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> chevaux;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL,mappedBy = "course")
private List<Pari> paris;
public boolean estEligiblePourTrioOrdre() {
return chevaux != null && chevaux.size() >= 3 && chevaux.size() <= 7;
}
}

View File

@@ -0,0 +1,38 @@
package com.pmu.betengine.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "hippodrome")
public class Hippodrome {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public String nom;
public String ville;
public String pays;
public boolean actif;
public Integer capacite;
public String description;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "hippodrome")
private List<Reunion> reunions;
}

View File

@@ -0,0 +1,108 @@
package com.pmu.betengine.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.statut.StatutParis;
import com.pmu.betengine.model.type.TypeFormule;
import com.pmu.betengine.model.type.TypeMulti;
import com.pmu.betengine.model.type.TypePari;
import jakarta.persistence.*;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "pari")
public class Pari{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String numeroTicket;
@Column(name = "type_pari")
private TypePari typePari;
@Column(name = "type_formule")
private TypeFormule typeFormule; // GAGNANT ou PLACE
private double mise = 500.0;
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date doit être au format dd/MM/yyyy HH:mm:ss")
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", shape = JsonFormat.Shape.STRING)
private LocalDateTime datePari;
private boolean estPaye;
private boolean estRembourse;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "course_id")
private Course course;
@Column(name = "parieur_id")
private String idParieur;
private String nomParieur;
private StatutParis statut;
private Double gain;
///////////////////Cheval////
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "cheval_id")
private Cheval cheval;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "cheval1_id")
private Cheval cheval1;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "cheval2_id")
private Cheval cheval2;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "cheval3_id")
private Cheval cheval3;
@ManyToOne(cascade = CascadeType.ALL)
private Cheval premier;
@ManyToOne(cascade = CascadeType.ALL)
private Cheval deuxieme;
@ManyToOne(cascade = CascadeType.ALL)
private Cheval troisieme;
// TRIPLET
@ElementCollection
@CollectionTable(name = "pari_chevaux_ordre", joinColumns = @JoinColumn(name = "pari_id"))
@Column(name = "cheval_id")
@OrderColumn(name = "position")
private List<Integer> chevauxOrdre;
// QUATRO, QUARTE PLUS, MULTI,
@ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinTable(name = "pari_chevaux",
joinColumns = @JoinColumn(name = "pari_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> chevauxSelectionnes;
@ElementCollection
private List<Integer> ordrePredit;
private Boolean validationOrdreExact;
// MULTI
@Enumerated(EnumType.STRING)
private TypeMulti typeMulti;
public boolean estFormuleChamp() {
return typeFormule.name().contains("CHAMP");
}
}

View File

@@ -0,0 +1,77 @@
package com.pmu.betengine.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "resultat")
public class Resultat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "course_id")
private Course course;
@ManyToMany
@CollectionTable(name = "resultat_ordre_arrivee")
@OrderColumn(name = "ordre_position")
private List<Cheval> ordreArrivee;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "resultat_premiers",
joinColumns = @JoinColumn(name = "resultat_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> premiers;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "resultat_seconds",
joinColumns = @JoinColumn(name = "resultat_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> seconds;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "resultat_troisiemes",
joinColumns = @JoinColumn(name = "resultat_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> troisiemes;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "resultat_quatriemes",
joinColumns = @JoinColumn(name = "resultat_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> quatriemes;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "resultat_cinquiemes",
joinColumns = @JoinColumn(name = "resultat_id"),
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
private List<Cheval> cinquiemes;
@ElementCollection
private List<Long> chevauxDeadHeat;
private boolean aDeadHeat;
@Column(name = "total_mises")
private double totalMises;
@Column(name = "masse_apartager")
private double masseAPartager;
@Column(name = "prelevements_legaux")
private double prelevementsLegaux;
@Column(name = "montant_rembourse")
private double montantRembourse;
@Column(name = "montant_cagnotte")
private double montantCagnotte;
}

View File

@@ -0,0 +1,44 @@
package com.pmu.betengine.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.statut.StatutReunion;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "reunion")
public class Reunion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public String code;
public String nom;
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
private LocalDateTime date;
public int numero;
public StatutReunion statut;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "hippodrome_id")
public Hippodrome hippodrome;
public Integer totalCourses;
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
@CreationTimestamp
private LocalDateTime createdAt;
@JsonFormat(pattern = "dd/MM/yyyy", shape = JsonFormat.Shape.STRING)
@UpdateTimestamp
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,67 @@
package com.pmu.betengine.model;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.model.type.TypeTPE;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "tpe", uniqueConstraints = {
@UniqueConstraint(columnNames = "imei"),
@UniqueConstraint(columnNames = "serial")
})
public class TPE {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "L'IMEI est obligatoire")
@Column(unique = true, nullable = false, length = 15)
private String imei;
@NotBlank(message = "Le numéro de série est obligatoire")
@Column(unique = true, nullable = false)
private String serial;
@Enumerated(EnumType.STRING)
@NotNull(message = "Le type est obligatoire")
@Column(nullable = false)
private TypeTPE type;
@NotBlank(message = "La marque est obligatoire")
@Column(nullable = false)
private String marque;
@NotBlank(message = "Le modèle est obligatoire")
@Column(nullable = false)
private String modele;
@Enumerated(EnumType.STRING)
@NotNull(message = "Le statut est obligatoire")
@Column(nullable = false)
private StatutTPE statut = StatutTPE.DISPONIBLE;
@Column(nullable = false)
private boolean assigne = false;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,13 @@
package com.pmu.betengine.model.dto;
import lombok.Data;
@Data
public class AgentFamilyMemberDTO {
private Long id;
private String nom;
private String statut;
private String dateNaissance;
private String sexe;
private String sexeLibelle; // Pour l'affichage
}

View File

@@ -0,0 +1,27 @@
package com.pmu.betengine.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
@Data
public class AgentFamilyMemberRequestDTO {
@NotBlank(message = "Le nom est obligatoire")
@Size(min = 2, max = 100, message = "Le nom doit contenir entre 2 et 100 caractères")
private String nom;
@NotBlank(message = "Le statut est obligatoire")
@Size(min = 2, max = 50, message = "Le statut doit contenir entre 2 et 50 caractères")
private String statut;
@NotBlank(message = "La date de naissance est obligatoire")
@Pattern(regexp = "^\\d{2}/\\d{2}/\\d{4}$", message = "La date de naissance doit être au format dd/MM/yyyy")
private LocalDate dateNaissance;
@NotBlank(message = "Le sexe est obligatoire")
@Pattern(regexp = "^(M|F)$", message = "Le sexe doit être M (Masculin) ou F (Féminin)")
private String sexe;
}

View File

@@ -0,0 +1,13 @@
package com.pmu.betengine.model.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;
}

View File

@@ -0,0 +1,18 @@
package com.pmu.betengine.model.dto;
import lombok.Data;
@Data
public class HippodromeDTO {
private Long id;
private String nom;
private String ville;
private String pays;
private boolean actif;
private Integer capacite;
private String description;
private Long reunionId;
private String reunionNom;
private String createdAt;
private String updatedAt;
}

View File

@@ -0,0 +1,30 @@
package com.pmu.betengine.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class HippodromeRequestDTO {
@NotBlank(message = "Le nom est obligatoire")
@Size(min = 2, max = 100, message = "Le nom doit contenir entre 2 et 100 caractères")
private String nom;
@NotBlank(message = "La ville est obligatoire")
@Size(min = 2, max = 50, message = "La ville doit contenir entre 2 et 50 caractères")
private String ville;
@NotBlank(message = "Le pays est obligatoire")
@Size(min = 2, max = 50, message = "Le pays doit contenir entre 2 et 50 caractères")
private String pays;
@NotNull(message = "Le statut actif est obligatoire")
private boolean actif;
private Integer capacite;
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
}

View File

@@ -0,0 +1,17 @@
package com.pmu.betengine.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.Reunion;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class NewCourse {
private String numero;
private Reunion reunion;
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", shape = JsonFormat.Shape.STRING)
private LocalDateTime heureCourse;
private String lieu;
}

View File

@@ -0,0 +1,46 @@
package com.pmu.betengine.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.Cheval;
import com.pmu.betengine.model.Course;
import com.pmu.betengine.model.statut.StatutParis;
import com.pmu.betengine.model.type.TypeFormule;
import com.pmu.betengine.model.type.TypeMulti;
import com.pmu.betengine.model.type.TypePari;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class NewPari {
private String numeroTicket;
private TypePari typePari;
private TypeFormule typeFormule; // GAGNANT ou PLACE
private double mise ;
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", shape = JsonFormat.Shape.STRING)
private LocalDateTime datePari;
private Long courseId;
private String idParieur;
private String nomParieur;
///////////////////Cheval////
private Integer cheval;
private Integer cheval1;
private Integer cheval2;
private Integer cheval3;
private Integer premier;
private Integer deuxieme;
private Integer troisieme;
// TRIPLET
private List<Integer> chevauxOrdre;
// QUATRO, QUARTE PLUS, MULTI,
private List<Integer> chevauxSelectionnes;
private List<Integer> ordrePredit;
private Boolean validationOrdreExact;
// MULTI
private TypeMulti typeMulti;
private StatutParis status;
private boolean estPaye;
private boolean estRembourse;
}

View File

@@ -0,0 +1,19 @@
package com.pmu.betengine.model.dto;
import lombok.Data;
import java.util.List;
@Data
public class NewResultat {
private Integer idCourse;
private List<Integer> chevauxPremiers;
private List<Integer> chevauxDeuxiemes;
private List<Integer> chevauxTroisiemes;
private List<Integer> chevauxQuatriemes;
private List<Integer> chevauxCinquiemes;
private List<Integer> ordreArrivee;
private boolean aDeadHeat;
}

View File

@@ -0,0 +1,25 @@
package com.pmu.betengine.model.dto;
import jakarta.persistence.Column;
import lombok.Data;
import java.util.List;
@Data
public class ResultatDto {
private Long idCourse;
private List<Long> chevauxPremiers;
private List<Long> chevauxDeuxiemes;
private List<Long> chevauxTroisiemes;
private List<Long> chevauxQuatriemes;
private List<Long> chevauxCinquiemes;
private boolean aDeadHeat;
private double totalMises;
private double masseAPartager;
private double prelevementsLegaux;
private double montantRembourse;
private double montantCagnotte;
}

View File

@@ -0,0 +1,17 @@
package com.pmu.betengine.model.dto;
import com.pmu.betengine.model.statut.StatutReunion;
import lombok.Data;
@Data
public class ReunionDTO {
private Long id;
private String code;
private String nom;
private String date;
private int numero;
private StatutReunion statut;
private Integer totalCourses;
// Getters/Setters
}

View File

@@ -0,0 +1,16 @@
package com.pmu.betengine.model.dto;
import com.pmu.betengine.model.statut.StatutReunion;
import lombok.Data;
@Data
public class ReunionRequestDTO {
private String code;
private String nom;
private String date;
private int numero;
private StatutReunion statut;
private Integer totalCourses;
// Getters/Setters
}

View File

@@ -0,0 +1,19 @@
package com.pmu.betengine.model.dto;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.model.type.TypeTPE;
import lombok.Data;
@Data
public class TPEDTO {
private Long id;
private String imei;
private String serial;
private TypeTPE type;
private String marque;
private String modele;
private StatutTPE statut;
private boolean assigne;
private String createdAt;
private String updatedAt;
}

View File

@@ -0,0 +1,36 @@
package com.pmu.betengine.model.dto;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.model.type.TypeTPE;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class TPERequestDTO {
@NotBlank(message = "L'IMEI est obligatoire")
@Pattern(regexp = "^[0-9]{15}$", message = "L'IMEI doit contenir exactement 15 chiffres")
private String imei;
@NotBlank(message = "Le numéro de série est obligatoire")
@Size(min = 2, max = 50, message = "Le numéro de série doit contenir entre 2 et 50 caractères")
private String serial;
@NotNull(message = "Le type est obligatoire")
private TypeTPE type;
@NotBlank(message = "La marque est obligatoire")
@Size(min = 2, max = 50, message = "La marque doit contenir entre 2 et 50 caractères")
private String marque;
@NotBlank(message = "Le modèle est obligatoire")
@Size(min = 2, max = 50, message = "Le modèle doit contenir entre 2 et 50 caractères")
private String modele;
@NotNull(message = "Le statut est obligatoire")
private StatutTPE statut;
private boolean assigne;
}

View File

@@ -0,0 +1,18 @@
package com.pmu.betengine.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.pmu.betengine.model.statut.StatutParis;
import com.pmu.betengine.model.type.TypeFormule;
import com.pmu.betengine.model.type.TypeMulti;
import com.pmu.betengine.model.type.TypePari;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class updatePari {
private StatutParis status;
private boolean estPaye;
private boolean estRembourse;
}

View File

@@ -0,0 +1,8 @@
package com.pmu.betengine.model.statut;
public enum StatutAgent {
ACTIF,
INACTIF,
SUSPENDU
}

View File

@@ -0,0 +1,11 @@
package com.pmu.betengine.model.statut;
public enum StatutCourse {
// PROGRAMMEE, EN_COURS, TERMINEE, ANNULEE
CREATED,
VALIDATED,
RUNNING,
CLOSED,
CANCELED
}

View File

@@ -0,0 +1,10 @@
package com.pmu.betengine.model.statut;
public enum StatutParis {
EN_ATTENTE,
GAGNANT,
PERDANT,
REMBOURSE,
SPECIAL_JUMELE,
SPECIAL_GAGNANT
}

View File

@@ -0,0 +1,8 @@
package com.pmu.betengine.model.statut;
public enum StatutResultat {
NONE,
CREATED,
VALIDATED,
CONFIRMED
}

View File

@@ -0,0 +1,8 @@
package com.pmu.betengine.model.statut;
public enum StatutReunion {
PLANIFIEE,
EN_COURS,
TERMINEE,
ANNULEE
}

View File

@@ -0,0 +1,13 @@
package com.pmu.betengine.model.statut;
public enum StatutTPE {
VALIDE,
INVALIDE,
EN_PANNE,
BLOQUE,
DISPONIBLE,
AFFECTE,
EN_MAINTENANCE,
HORS_SERVICE,
VOLE
}

View File

@@ -0,0 +1,12 @@
package com.pmu.betengine.model.type;
public enum TypeDeadHeat {
QUATRE_PREMIERS_OU_PLUS,
TROIS_PREMIERS_UN_QUATRIEME,
DEUX_PREMIERS_DEUX_TROISIEMES,
DEUX_PREMIERS_UN_TROISIEME_UN_QUATRIEME,
TROIS_SECONDS_OU_PLUS,
DEUX_SECONDS_UN_QUATRIEME,
DEUX_TROISIEMES_OU_PLUS,
DEUX_QUATRIEMES_OU_PLUS
}

View File

@@ -0,0 +1,33 @@
package com.pmu.betengine.model.type;
public enum TypeFormule {
GAGNANT,
PLACE,
UNITAIRE,
COMBINEE_COMPLETE,
COMBINEE_SIMPLIFIEE,
CHAMP_TOTAL,
CHAMP_PARTIEL,
TRIPLET_ORDRE_EXACT,
TRIPLET_ORDRE_INEXACT,
FORMULE_COMPLETE,
FORMULE_SIMPLIFIEE,
CHAMP_TOTAL_2_CHEVAUX_COMPLET,
CHAMP_TOTAL_2_CHEVAUX_SIMPLIFIE,
CHAMP_PARTIEL_2_CHEVAUX_COMPLET,
CHAMP_PARTIEL_2_CHEVAUX_SIMPLIFIE,
CHAMP_TOTAL_1_CHEVAL_COMPLET,
CHAMP_TOTAL_1_CHEVAL_SIMPLIFIE,
CHAMP_PARTIEL_1_CHEVAL_COMPLET,
CHAMP_PARTIEL_1_CHEVAL_SIMPLIFIE,
COMBINE,
CHAMP_TOTAL_3,
CHAMP_PARTIEL_3,
CHAMP_TOTAL_2,
CHAMP_PARTIEL_2,
CHAMP_TOTAL_1,
CHAMP_PARTIEL_1,
}

View File

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

View File

@@ -0,0 +1,19 @@
package com.pmu.betengine.model.type;
public enum TypePaiement {
QUATRO,
SPECIAL_TRIO,
SPECIAL_JUMELE,
SPECIAL_GAGNANT,
REMBOURSEMENT,
QUARTE_PLUS_ORDRE_EXACT,
QUARTE_PLUS_ORDRE_INEXACT,
BONUS_3,
BONUS_3_BIS,
MULTI_4,
MULTI_5,
MULTI_6,
MULTI_7,
}

View File

@@ -0,0 +1,15 @@
package com.pmu.betengine.model.type;
public enum TypePari {
SIMPLE,
JUMELE_GAGNANT,
JUMELE_PLACE,
JUMELO_ORDRE,
TRIO,
TRIO_ORDRE,
TRIPLET,
QUATRO,
QUARTE_PLUS,
MULTI,
QUINTE_PLUS
}

View File

@@ -0,0 +1,6 @@
package com.pmu.betengine.model.type;
public enum TypeTPE {
POS,
OTHER
}

View File

@@ -0,0 +1,28 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.AgentFamilyMember;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Repository
public interface AgentFamilyMemberRepository extends JpaRepository<AgentFamilyMember, Long> {
List<AgentFamilyMember> findByNomContainingIgnoreCase(String nom);
List<AgentFamilyMember> findByStatut(String statut);
List<AgentFamilyMember> findBySexe(String sexe);
@Query("SELECT a FROM AgentFamilyMember a WHERE a.nom LIKE %:keyword% OR a.statut LIKE %:keyword%")
List<AgentFamilyMember> searchByKeyword(String keyword);
boolean existsByNomAndDateNaissance(String nom, LocalDate dateNaissance);
@Query("SELECT a FROM AgentFamilyMember a ORDER BY a.nom ASC")
List<AgentFamilyMember> findAllOrderByNom();
}

View File

@@ -0,0 +1,15 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.Cheval;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface ChevalRepository extends JpaRepository<Cheval, Long> {
//List<Cheval> findByCourseId(Long courseId);
List<Cheval> findByNomEcurie(String nomEcurie);
Optional<Cheval> getChevalByNumero (int numero);
}

View File

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

View File

@@ -0,0 +1,28 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.Hippodrome;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface HippodromeRepository extends JpaRepository<Hippodrome, Long> {
Optional<Hippodrome> findByNom(String nom);
List<Hippodrome> findByVille(String ville);
List<Hippodrome> findByPays(String pays);
List<Hippodrome> findByActif(boolean actif);
boolean existsByNom(String nom);
@Query("SELECT h FROM Hippodrome h WHERE h.nom LIKE %:nom%")
List<Hippodrome> findByNomContaining(String nom);
Optional<Hippodrome> findByReunionId(Long reunionId);
}

View File

@@ -0,0 +1,19 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.Pari;
import com.pmu.betengine.model.type.TypeFormule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface PariRepository extends JpaRepository<Pari, Long> {
List<Pari> findByCourseId(Long courseId);
List<Pari> findByCourseIdAndTypeFormule(Long courseId, TypeFormule typeFormule);
List<Pari> findByChevalId(Long chevalId);
Optional<Pari> findByNumeroTicket(String ticket);
// List<Pari> findByChevalIdAndTypePari(Long chevalId, String typePari);
}

View File

@@ -0,0 +1,25 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.Resultat;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ResultatRepository extends JpaRepository<Resultat, Long> {
Optional<Resultat> findByCourseId(Long courseId);
@Query("SELECT r FROM Resultat r WHERE r.course.id = :courseId")
Optional<Resultat> findByCourse(@Param("courseId") Long courseId);
@Query("SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END FROM Resultat r WHERE r.course.id = :courseId")
boolean existsByCourseId(@Param("courseId") Long courseId);
@Query("DELETE FROM Resultat r WHERE r.course.id = :courseId")
void deleteByCourseId(@Param("courseId") Long courseId);
}

View File

@@ -0,0 +1,13 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.Reunion;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ReunionRepository extends JpaRepository<Reunion, Long> {
Optional<Reunion> findByCode(String code);
boolean existsByCode(String code);
}

View File

@@ -0,0 +1,42 @@
package com.pmu.betengine.repository;
import com.pmu.betengine.model.TPE;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.model.type.TypeTPE;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface TPERepository extends JpaRepository<TPE, Long> {
Optional<TPE> findByImei(String imei);
Optional<TPE> findBySerial(String serial);
boolean existsByImei(String imei);
boolean existsBySerial(String serial);
List<TPE> findByType(TypeTPE type);
List<TPE> findByStatut(StatutTPE statut);
List<TPE> findByMarque(String marque);
List<TPE> findByAssigne(boolean assigne);
@Query("SELECT t FROM TPE t WHERE t.imei LIKE %:searchTerm% OR t.serial LIKE %:searchTerm% OR t.marque LIKE %:searchTerm% OR t.modele LIKE %:searchTerm%")
List<TPE> searchByTerm(String searchTerm);
@Query("SELECT t FROM TPE t WHERE t.statut = 'DISPONIBLE' AND t.assigne = false")
List<TPE> findAvailableTPE();
long countByStatut(StatutTPE statut);
long countByAssigne(boolean assigne);
}

View File

@@ -0,0 +1,121 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.AgentFamilyMember;
import com.pmu.betengine.model.dto.AgentFamilyMemberDTO;
import com.pmu.betengine.model.dto.AgentFamilyMemberRequestDTO;
import com.pmu.betengine.repository.AgentFamilyMemberRepository;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class AgentFamilyMemberService {
private final AgentFamilyMemberRepository agentFamilyMemberRepository;
private final ModelMapper modelMapper;
public AgentFamilyMemberService(AgentFamilyMemberRepository agentFamilyMemberRepository,
ModelMapper modelMapper) {
this.agentFamilyMemberRepository = agentFamilyMemberRepository;
this.modelMapper = modelMapper;
}
public List<AgentFamilyMemberDTO> getAllFamilyMembers() {
return agentFamilyMemberRepository.findAllOrderByNom()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public AgentFamilyMemberDTO getFamilyMemberById(Long id) {
AgentFamilyMember member = agentFamilyMemberRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Membre de famille non trouvé avec l'id: " + id));
return convertToDTO(member);
}
public AgentFamilyMemberDTO createFamilyMember(AgentFamilyMemberRequestDTO requestDTO) {
// Vérifier si le membre existe déjà
if (agentFamilyMemberRepository.existsByNomAndDateNaissance(requestDTO.getNom(), requestDTO.getDateNaissance())) {
throw new RuntimeException("Un membre avec ce nom et cette date de naissance existe déjà");
}
AgentFamilyMember member = convertToEntity(requestDTO);
AgentFamilyMember saved = agentFamilyMemberRepository.save(member);
return convertToDTO(saved);
}
public AgentFamilyMemberDTO updateFamilyMember(Long id, AgentFamilyMemberRequestDTO requestDTO) {
AgentFamilyMember existing = agentFamilyMemberRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Membre de famille non trouvé avec l'id: " + id));
// Vérifier les doublons (sauf pour l'enregistrement actuel)
if (!existing.getNom().equals(requestDTO.getNom()) ||
!existing.getDateNaissance().equals(requestDTO.getDateNaissance())) {
if (agentFamilyMemberRepository.existsByNomAndDateNaissance(requestDTO.getNom(), requestDTO.getDateNaissance())) {
throw new RuntimeException("Un membre avec ce nom et cette date de naissance existe déjà");
}
}
// Mise à jour des champs
modelMapper.map(requestDTO, existing);
return convertToDTO(agentFamilyMemberRepository.save(existing));
}
public void deleteFamilyMember(Long id) {
if (!agentFamilyMemberRepository.existsById(id)) {
throw new RuntimeException("Membre de famille non trouvé avec l'id: " + id);
}
agentFamilyMemberRepository.deleteById(id);
}
public List<AgentFamilyMemberDTO> getFamilyMembersByStatut(String statut) {
return agentFamilyMemberRepository.findByStatut(statut)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AgentFamilyMemberDTO> getFamilyMembersBySexe(String sexe) {
return agentFamilyMemberRepository.findBySexe(sexe)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AgentFamilyMemberDTO> searchFamilyMembers(String keyword) {
return agentFamilyMemberRepository.searchByKeyword(keyword)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AgentFamilyMemberDTO> getFamilyMembersByNom(String nom) {
return agentFamilyMemberRepository.findByNomContainingIgnoreCase(nom)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
private AgentFamilyMemberDTO convertToDTO(AgentFamilyMember member) {
AgentFamilyMemberDTO dto = modelMapper.map(member, AgentFamilyMemberDTO.class);
// Ajouter le libellé du sexe
if ("M".equals(member.getSexe())) {
dto.setSexeLibelle("Masculin");
} else if ("F".equals(member.getSexe())) {
dto.setSexeLibelle("Féminin");
}
return dto;
}
private AgentFamilyMember convertToEntity(AgentFamilyMemberRequestDTO dto) {
return modelMapper.map(dto, AgentFamilyMember.class);
}
}

View File

@@ -0,0 +1,27 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Cheval;
import com.pmu.betengine.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 null;// chevalRepository.findByCourseId(courseId);
}
public List<Cheval> obtenirChevauxParEcurie(String nomEcurie) {
return chevalRepository.findByNomEcurie(nomEcurie);
}
}

View File

@@ -0,0 +1,37 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Course;
import com.pmu.betengine.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);
}
}

View File

@@ -0,0 +1,121 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Hippodrome;
import com.pmu.betengine.model.Reunion;
import com.pmu.betengine.model.dto.HippodromeDTO;
import com.pmu.betengine.model.dto.HippodromeRequestDTO;
import com.pmu.betengine.repository.HippodromeRepository;
import com.pmu.betengine.repository.ReunionRepository;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class HippodromeService {
private final HippodromeRepository hippodromeRepository;
private final ReunionRepository reunionRepository;
private final ModelMapper modelMapper;
public HippodromeService(HippodromeRepository hippodromeRepository,
ReunionRepository reunionRepository,
ModelMapper modelMapper) {
this.hippodromeRepository = hippodromeRepository;
this.reunionRepository = reunionRepository;
this.modelMapper = modelMapper;
}
public List<HippodromeDTO> getAllHippodromes() {
return hippodromeRepository.findAll()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public HippodromeDTO getHippodromeById(Long id) {
Hippodrome hippodrome = hippodromeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Hippodrome non trouvé avec l'id: " + id));
return convertToDTO(hippodrome);
}
public HippodromeDTO createHippodrome(HippodromeRequestDTO requestDTO) {
if (hippodromeRepository.existsByNom(requestDTO.getNom())) {
throw new RuntimeException("Un hippodrome avec ce nom existe déjà: " + requestDTO.getNom());
}
Hippodrome hippodrome = convertToEntity(requestDTO);
Hippodrome saved = hippodromeRepository.save(hippodrome);
return convertToDTO(saved);
}
public HippodromeDTO updateHippodrome(Long id, HippodromeRequestDTO requestDTO) {
Hippodrome existing = hippodromeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Hippodrome non trouvé avec l'id: " + id));
// Vérification du nom si modifié
if (!existing.getNom().equals(requestDTO.getNom()) &&
hippodromeRepository.existsByNom(requestDTO.getNom())) {
throw new RuntimeException("Un hippodrome avec ce nom existe déjà: " + requestDTO.getNom());
}
// Mise à jour des champs
modelMapper.map(requestDTO, existing);
return convertToDTO(hippodromeRepository.save(existing));
}
public void deleteHippodrome(Long id) {
if (!hippodromeRepository.existsById(id)) {
throw new RuntimeException("Hippodrome non trouvé avec l'id: " + id);
}
hippodromeRepository.deleteById(id);
}
public List<HippodromeDTO> getHippodromesByVille(String ville) {
return hippodromeRepository.findByVille(ville)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<HippodromeDTO> getHippodromesActifs() {
return hippodromeRepository.findByActif(true)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public HippodromeDTO getHippodromeByReunion(Long reunionId) {
Hippodrome hippodrome = hippodromeRepository.findByReunionId(reunionId)
.orElseThrow(() -> new RuntimeException("Aucun hippodrome trouvé pour la réunion avec l'id: " + reunionId));
return convertToDTO(hippodrome);
}
private HippodromeDTO convertToDTO(Hippodrome hippodrome) {
HippodromeDTO dto = modelMapper.map(hippodrome, HippodromeDTO.class);
// Formatage des dates
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
if (hippodrome.getCreatedAt() != null) {
dto.setCreatedAt(hippodrome.getCreatedAt().format(formatter));
}
if (hippodrome.getUpdatedAt() != null) {
dto.setUpdatedAt(hippodrome.getUpdatedAt().format(formatter));
}
return dto;
}
private Hippodrome convertToEntity(HippodromeRequestDTO dto) {
return modelMapper.map(dto, Hippodrome.class);
}
}

View File

@@ -0,0 +1,67 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Course;
import com.pmu.betengine.model.Pari;
import com.pmu.betengine.model.statut.StatutParis;
import com.pmu.betengine.model.type.TypeFormule;
import com.pmu.betengine.model.type.TypePari;
import com.pmu.betengine.repository.ChevalRepository;
import com.pmu.betengine.repository.CourseRepository;
import com.pmu.betengine.repository.PariRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PariService {
@Autowired
private PariRepository pariRepository;
@Autowired
private CourseRepository courseRepository;
@Autowired
private ChevalRepository chevalRepository;
private static final double MISE_DE_BASE = 500.0;
public Pari placerPari(Pari pari) {
TypePari tp= pari.getTypePari();
// Vérifier que le type de pari est valide pour la course
pari.setStatut(StatutParis.EN_ATTENTE);
Course course = (Course) pari.getCourse();
if ("GAGNANT".equals(pari.getTypeFormule().name()) && course.getNombreChevauxInscrits() < 2) {
throw new IllegalArgumentException("Pari GAGNANT impossible: moins de 2 chevaux");
}
if ("PLACE".equals(pari.getTypeFormule().name()) && course.getNombreChevauxInscrits() < 3) {
throw new IllegalArgumentException("Pari PLACE impossible: moins de 3 chevaux");
}
return pariRepository.save(pari);
}
public Pari update(Pari pari){
return pariRepository.save(pari);
}
public Pari findByTicket(String ticket){
return pariRepository.findByNumeroTicket(ticket).orElse(null);
}
public List<Pari> obtenirParisParCourse(Long courseId) {
return pariRepository.findByCourseId(courseId);
}
public List<Pari> obtenirParisParCourseEtType(Long courseId, TypeFormule typeFormule) {
return pariRepository.findByCourseIdAndTypeFormule(courseId, typeFormule);
}
public List<Pari> obtenirParisParCheval(Long chevalId) {
return pariRepository.findByChevalId(chevalId);
}
}

View File

@@ -0,0 +1,27 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Resultat;
import com.pmu.betengine.repository.ResultatRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class ResultatService {
private final ResultatRepository resultatCourseRepository;
public Optional<Resultat> findByCourseId(Long courseId) {
return resultatCourseRepository.findByCourseId(courseId);
}
public Resultat save(Resultat resultatCourse) {
return resultatCourseRepository.save(resultatCourse);
}
public void deleteByCourseId(Long courseId) {
resultatCourseRepository.deleteByCourseId(courseId);
}
}

View File

@@ -0,0 +1,71 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.Reunion;
import com.pmu.betengine.model.dto.ReunionDTO;
import com.pmu.betengine.model.dto.ReunionRequestDTO;
import com.pmu.betengine.repository.ReunionRepository;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class ReunionService {
private final ReunionRepository reunionRepository;
private final ModelMapper modelMapper;
public ReunionService(ReunionRepository reunionRepository, ModelMapper modelMapper) {
this.reunionRepository = reunionRepository;
this.modelMapper = modelMapper;
}
public List<ReunionDTO> getAllReunions() {
return reunionRepository.findAll()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public ReunionDTO getReunionById(Long id) {
Reunion reunion = reunionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Reunion non trouvée"));
return convertToDTO(reunion);
}
public ReunionDTO createReunion(ReunionRequestDTO requestDTO) {
if (reunionRepository.existsByCode(requestDTO.getCode())) {
throw new RuntimeException("Code déjà existant");
}
Reunion reunion = convertToEntity(requestDTO);
reunion.setCreatedAt(LocalDateTime.now());
Reunion saved = reunionRepository.save(reunion);
return convertToDTO(saved);
}
public ReunionDTO updateReunion(Long id, ReunionRequestDTO requestDTO) {
Reunion existing = reunionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Reunion non trouvée"));
modelMapper.map(requestDTO, existing);
existing.setUpdatedAt(LocalDateTime.now());
return convertToDTO(reunionRepository.save(existing));
}
public void deleteReunion(Long id) {
reunionRepository.deleteById(id);
}
private ReunionDTO convertToDTO(Reunion reunion) {
return modelMapper.map(reunion, ReunionDTO.class);
}
private Reunion convertToEntity(ReunionRequestDTO dto) {
return modelMapper.map(dto, Reunion.class);
}
}

View File

@@ -0,0 +1,177 @@
package com.pmu.betengine.service;
import com.pmu.betengine.model.TPE;
import com.pmu.betengine.model.dto.TPEDTO;
import com.pmu.betengine.model.dto.TPERequestDTO;
import com.pmu.betengine.model.statut.StatutTPE;
import com.pmu.betengine.repository.TPERepository;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class TPEService {
private final TPERepository tpeRepository;
private final ModelMapper modelMapper;
public TPEService(TPERepository tpeRepository, ModelMapper modelMapper) {
this.tpeRepository = tpeRepository;
this.modelMapper = modelMapper;
}
public List<TPEDTO> getAllTPEs() {
return tpeRepository.findAll()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public TPEDTO getTPEById(Long id) {
TPE tpe = tpeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("TPE non trouvé avec l'id: " + id));
return convertToDTO(tpe);
}
public TPEDTO createTPE(TPERequestDTO requestDTO) {
// Vérifier l'unicité de l'IMEI
if (tpeRepository.existsByImei(requestDTO.getImei())) {
throw new RuntimeException("Un TPE avec cet IMEI existe déjà: " + requestDTO.getImei());
}
// Vérifier l'unicité du numéro de série
if (tpeRepository.existsBySerial(requestDTO.getSerial())) {
throw new RuntimeException("Un TPE avec ce numéro de série existe déjà: " + requestDTO.getSerial());
}
TPE tpe = convertToEntity(requestDTO);
TPE saved = tpeRepository.save(tpe);
return convertToDTO(saved);
}
public TPEDTO updateTPE(Long id, TPERequestDTO requestDTO) {
TPE existing = tpeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("TPE non trouvé avec l'id: " + id));
// Vérifier l'unicité de l'IMEI si modifié
if (!existing.getImei().equals(requestDTO.getImei()) &&
tpeRepository.existsByImei(requestDTO.getImei())) {
throw new RuntimeException("Un TPE avec cet IMEI existe déjà: " + requestDTO.getImei());
}
// Vérifier l'unicité du numéro de série si modifié
if (!existing.getSerial().equals(requestDTO.getSerial()) &&
tpeRepository.existsBySerial(requestDTO.getSerial())) {
throw new RuntimeException("Un TPE avec ce numéro de série existe déjà: " + requestDTO.getSerial());
}
// Mise à jour des champs
modelMapper.map(requestDTO, existing);
return convertToDTO(tpeRepository.save(existing));
}
public void deleteTPE(Long id) {
if (!tpeRepository.existsById(id)) {
throw new RuntimeException("TPE non trouvé avec l'id: " + id);
}
tpeRepository.deleteById(id);
}
public TPEDTO updateStatut(Long id, StatutTPE statut) {
TPE tpe = tpeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("TPE non trouvé avec l'id: " + id));
tpe.setStatut(statut);
// Si le statut est AFFECTE, mettre à jour le champ assigne
if (statut == StatutTPE.AFFECTE) {
tpe.setAssigne(true);
} else if (statut == StatutTPE.DISPONIBLE) {
tpe.setAssigne(false);
}
return convertToDTO(tpeRepository.save(tpe));
}
public TPEDTO assignerTPE(Long id) {
TPE tpe = tpeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("TPE non trouvé avec l'id: " + id));
if (tpe.isAssigne()) {
throw new RuntimeException("Le TPE est déjà assigné");
}
tpe.setAssigne(true);
tpe.setStatut(StatutTPE.AFFECTE);
return convertToDTO(tpeRepository.save(tpe));
}
public TPEDTO libererTPE(Long id) {
TPE tpe = tpeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("TPE non trouvé avec l'id: " + id));
if (!tpe.isAssigne()) {
throw new RuntimeException("Le TPE n'est pas assigné");
}
tpe.setAssigne(false);
tpe.setStatut(StatutTPE.DISPONIBLE);
return convertToDTO(tpeRepository.save(tpe));
}
public List<TPEDTO> getTPEsByStatut(StatutTPE statut) {
return tpeRepository.findByStatut(statut)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<TPEDTO> getTPEsDisponibles() {
return tpeRepository.findAvailableTPE()
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<TPEDTO> searchTPEs(String searchTerm) {
return tpeRepository.searchByTerm(searchTerm)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public long countTPEsByStatut(StatutTPE statut) {
return tpeRepository.countByStatut(statut);
}
public long countTPEsAssignes() {
return tpeRepository.countByAssigne(true);
}
private TPEDTO convertToDTO(TPE tpe) {
TPEDTO dto = modelMapper.map(tpe, TPEDTO.class);
// Formatage des dates
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
if (tpe.getCreatedAt() != null) {
dto.setCreatedAt(tpe.getCreatedAt().format(formatter));
}
if (tpe.getUpdatedAt() != null) {
dto.setUpdatedAt(tpe.getUpdatedAt().format(formatter));
}
return dto;
}
private TPE convertToEntity(TPERequestDTO dto) {
return modelMapper.map(dto, TPE.class);
}
}

View File

@@ -0,0 +1,28 @@
package com.pmu.betengine.util;
import com.pmu.betengine.model.Cheval;
import java.util.ArrayList;
import java.util.List;
public class ChevalUtil {
public static Cheval numeroToCheval(Integer numero){
Cheval cheval = new Cheval();
cheval.setNumero(numero);
cheval.setNonPartant(false);
return cheval;
}
public static List<Cheval> fromIntegerList(List<Integer> ln){
List<Cheval> lc=new ArrayList<>();
if(ln !=null) {
for (Integer n : ln
) {
lc.add(numeroToCheval(n));
}
}
return lc;
}
}

View File

@@ -0,0 +1,27 @@
spring.application.name=PMU API PLR
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui-custom.html
#spring.datasource.url=jdbc:postgresql://localhost:5432/dmp
# Configuration de la source de donn<6E>es (DataSource)
spring.datasource.url=jdbc:postgresql://api_db:5432/plr
spring.datasource.username=postgres
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
# Configuration de JPA (Hibernate)
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql= true
spring.mail.host: smtp.gmail.com
spring.mail.port: 587
spring.mail.username: ton.email@gmail.com
spring.mail.password: ton_mot_de_passe
spring.mail.properties.mail.smtp.auth: true
spring.mail.properties.mail.smtp.starttls:
spring.mail.properties.mail.smtp.enable: true
logging.level.org.hibernate=ERROR

View File

@@ -0,0 +1,16 @@
___ _ ___ _ __ ___ _ _ ___ ___ ___ ___ __ __ ___ ___ _ ___ ___ ___ _ ___
| _ ) /_\ / __| | |/ / | __| | \| | | \ / __| | __| | _ \ \ \ / / | __| | _ \ /_\ | _ \ |_ _| | _ \ | | | _ \
| _ \ / _ \ | (__ | ' < | _| | .` | | |) | \__ \ | _| | / \ V / | _| | / / _ \ | _/ | | | _/ | |__ | /
|___/ /_/ \_\ \___| |_|\_\ |___| |_|\_| |___/ |___/ |___| |_|_\ \_/ |___| |_|_\ /_/ \_\ |_| |___| |_| |____| |_|_\
___ __ __
| _ ) \ \ / /
| _ \ \ V /
|___/ |_|
___ __ __ _ _ __ __ _ _ ___
| _ \ | \/ | | | | | | \/ | /_\ | | |_ _|
| _/ | |\/| | | |_| | | |\/| | / _ \ | |__ | |
|_| |_| |_| \___/ |_| |_| /_/ \_\ |____| |___|