Refactor: Backend Frontend-Centric Auth - Suppression OIDC, validation JWT
Architecture modifiée pour Frontend-Centric Authentication: 1. **Suppression des dépendances OIDC** - quarkus-oidc → quarkus-smallrye-jwt - quarkus-keycloak-authorization → quarkus-smallrye-jwt-build - Le backend ne gère plus l'authentification OAuth 2. **Configuration JWT simple** - Validation des tokens JWT envoyés par le frontend - mp.jwt.verify.publickey.location (JWKS de Keycloak) - mp.jwt.verify.issuer (Keycloak realm) - Authentification via Authorization: Bearer header 3. **Suppression configurations OIDC** - application.properties: Suppression %dev.quarkus.oidc.* - application.properties: Suppression %prod.quarkus.oidc.* - application-prod.properties: Remplacement par mp.jwt.* - Logging: io.quarkus.oidc → io.quarkus.smallrye.jwt 4. **Sécurité simplifiée** - quarkus.security.auth.proactive=false - @Authenticated sur les endpoints - CORS configuré pour le frontend - Endpoints publics: /q/*, /openapi, /swagger-ui/* Flux d'authentification: 1️⃣ Frontend → Keycloak (OAuth login) 2️⃣ Frontend ← Keycloak (access_token) 3️⃣ Frontend → Backend (Authorization: Bearer token) 4️⃣ Backend valide le token JWT (signature + issuer) 5️⃣ Backend → Frontend (données API) Avantages: ✅ Pas de secret backend à gérer ✅ Pas de client btpxpress-backend dans Keycloak ✅ Séparation claire frontend/backend ✅ Backend devient une API REST stateless ✅ Tokens gérés par le frontend (localStorage/sessionStorage) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.AbonnementService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Abonnement;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutAbonnement;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeAbonnement;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des abonnements
|
||||
* Architecture 2025 : API complète pour la gestion des abonnements
|
||||
*/
|
||||
@Path("/api/v1/abonnements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Abonnements", description = "Gestion des abonnements d'entreprise")
|
||||
public class AbonnementResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbonnementResource.class);
|
||||
|
||||
@Inject AbonnementService abonnementService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les abonnements")
|
||||
@APIResponse(responseCode = "200", description = "Liste des abonnements récupérée avec succès")
|
||||
public Response getAllAbonnements(
|
||||
@Parameter(description = "Statut") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type d'abonnement") @QueryParam("type") String type) {
|
||||
logger.debug("GET /abonnements - statut: {}, type: {}", statut, type);
|
||||
|
||||
List<Abonnement> abonnements;
|
||||
|
||||
if (statut != null && !statut.trim().isEmpty()) {
|
||||
try {
|
||||
StatutAbonnement statutEnum = StatutAbonnement.valueOf(statut.toUpperCase());
|
||||
abonnements = abonnementService.findByStatut(statutEnum);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Statut invalide: " + statut))
|
||||
.build();
|
||||
}
|
||||
} else if (type != null && !type.trim().isEmpty()) {
|
||||
try {
|
||||
TypeAbonnement typeEnum = TypeAbonnement.valueOf(type.toUpperCase());
|
||||
abonnements = abonnementService.findByType(typeEnum);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Type invalide: " + type))
|
||||
.build();
|
||||
}
|
||||
} else {
|
||||
abonnements = abonnementService.findAll();
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("abonnements", abonnements);
|
||||
response.put("total", abonnements.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un abonnement par ID")
|
||||
@APIResponse(responseCode = "200", description = "Abonnement trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response getAbonnementById(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.debug("GET /abonnements/{}", id);
|
||||
Abonnement abonnement = abonnementService.findByIdRequired(id);
|
||||
return Response.ok(abonnement).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/actifs")
|
||||
@Operation(summary = "Récupérer tous les abonnements actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des abonnements actifs")
|
||||
public Response getAbonnementsActifs() {
|
||||
logger.debug("GET /abonnements/actifs");
|
||||
List<Abonnement> abonnements = abonnementService.findActifs();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("abonnements", abonnements);
|
||||
response.put("total", abonnements.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/expires")
|
||||
@Operation(summary = "Récupérer tous les abonnements expirés")
|
||||
@APIResponse(responseCode = "200", description = "Liste des abonnements expirés")
|
||||
public Response getAbonnementsExpires() {
|
||||
logger.debug("GET /abonnements/expires");
|
||||
List<Abonnement> abonnements = abonnementService.findExpires();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("abonnements", abonnements);
|
||||
response.put("total", abonnements.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/bientot-expires")
|
||||
@Operation(summary = "Récupérer les abonnements qui arrivent à expiration")
|
||||
@APIResponse(responseCode = "200", description = "Liste des abonnements bientôt expirés")
|
||||
public Response getAbonnementsBientotExpires(
|
||||
@Parameter(description = "Nombre de jours") @QueryParam("jours") @DefaultValue("7") int jours) {
|
||||
logger.debug("GET /abonnements/bientot-expires - jours: {}", jours);
|
||||
List<Abonnement> abonnements = abonnementService.findBientotExpires(jours);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("abonnements", abonnements);
|
||||
response.put("total", abonnements.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/entreprise/{entrepriseId}")
|
||||
@Operation(summary = "Récupérer l'abonnement actif d'une entreprise")
|
||||
@APIResponse(responseCode = "200", description = "Abonnement actif trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Aucun abonnement actif pour cette entreprise")
|
||||
public Response getAbonnementActifByEntreprise(
|
||||
@Parameter(description = "ID de l'entreprise") @PathParam("entrepriseId") UUID entrepriseId) {
|
||||
logger.debug("GET /abonnements/entreprise/{}", entrepriseId);
|
||||
|
||||
return abonnementService
|
||||
.findAbonnementActifByEntreprise(entrepriseId)
|
||||
.map(abonnement -> Response.ok(abonnement).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Aucun abonnement actif pour cette entreprise"))
|
||||
.build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/entreprise/{entrepriseId}/historique")
|
||||
@Operation(summary = "Récupérer l'historique des abonnements d'une entreprise")
|
||||
@APIResponse(responseCode = "200", description = "Historique récupéré avec succès")
|
||||
public Response getHistoriqueByEntreprise(
|
||||
@Parameter(description = "ID de l'entreprise") @PathParam("entrepriseId") UUID entrepriseId) {
|
||||
logger.debug("GET /abonnements/entreprise/{}/historique", entrepriseId);
|
||||
List<Abonnement> abonnements = abonnementService.findByEntreprise(entrepriseId);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("abonnements", abonnements);
|
||||
response.put("total", abonnements.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistics")
|
||||
@Operation(summary = "Récupérer les statistiques des abonnements")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistics() {
|
||||
logger.debug("GET /abonnements/statistics");
|
||||
Map<String, Object> stats = abonnementService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/plans")
|
||||
@Operation(summary = "Récupérer les plans tarifaires disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Plans récupérés avec succès")
|
||||
public Response getPlans() {
|
||||
logger.debug("GET /abonnements/plans");
|
||||
Map<String, Object> plans = abonnementService.getPlans();
|
||||
return Response.ok(plans).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION ===
|
||||
|
||||
@POST
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer un nouvel abonnement")
|
||||
@APIResponse(responseCode = "201", description = "Abonnement créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Un abonnement actif existe déjà")
|
||||
public Response createAbonnement(@Valid @NotNull Abonnement abonnement) {
|
||||
logger.info("POST /abonnements - Création d'un abonnement");
|
||||
Abonnement created = abonnementService.create(abonnement);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/mensuel")
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer un abonnement mensuel")
|
||||
@APIResponse(responseCode = "201", description = "Abonnement mensuel créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createAbonnementMensuel(
|
||||
@Parameter(description = "ID de l'entreprise") @QueryParam("entrepriseId") @NotNull UUID entrepriseId,
|
||||
@Parameter(description = "Type d'abonnement") @QueryParam("type") @NotNull String type,
|
||||
@Parameter(description = "Méthode de paiement") @QueryParam("methodePaiement") String methodePaiement) {
|
||||
logger.info(
|
||||
"POST /abonnements/mensuel - entrepriseId: {}, type: {}", entrepriseId, type);
|
||||
|
||||
try {
|
||||
TypeAbonnement typeEnum = TypeAbonnement.valueOf(type.toUpperCase());
|
||||
Abonnement created =
|
||||
abonnementService.createAbonnementMensuel(entrepriseId, typeEnum, methodePaiement);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Type d'abonnement invalide: " + type))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/annuel")
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer un abonnement annuel")
|
||||
@APIResponse(responseCode = "201", description = "Abonnement annuel créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createAbonnementAnnuel(
|
||||
@Parameter(description = "ID de l'entreprise") @QueryParam("entrepriseId") @NotNull UUID entrepriseId,
|
||||
@Parameter(description = "Type d'abonnement") @QueryParam("type") @NotNull String type,
|
||||
@Parameter(description = "Méthode de paiement") @QueryParam("methodePaiement") String methodePaiement) {
|
||||
logger.info("POST /abonnements/annuel - entrepriseId: {}, type: {}", entrepriseId, type);
|
||||
|
||||
try {
|
||||
TypeAbonnement typeEnum = TypeAbonnement.valueOf(type.toUpperCase());
|
||||
Abonnement created =
|
||||
abonnementService.createAbonnementAnnuel(entrepriseId, typeEnum, methodePaiement);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Type d'abonnement invalide: " + type))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MISE À JOUR ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Mettre à jour un abonnement")
|
||||
@APIResponse(responseCode = "200", description = "Abonnement mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response updateAbonnement(
|
||||
@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id,
|
||||
@Valid @NotNull Abonnement abonnementUpdate) {
|
||||
logger.info("PUT /abonnements/{}", id);
|
||||
Abonnement updated = abonnementService.update(id, abonnementUpdate);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/renouveler")
|
||||
@Authenticated
|
||||
@Operation(summary = "Renouveler un abonnement")
|
||||
@APIResponse(responseCode = "200", description = "Abonnement renouvelé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response renouvelerAbonnement(
|
||||
@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Renouvellement annuel") @QueryParam("annuel") @DefaultValue("false") boolean annuel) {
|
||||
logger.info("POST /abonnements/{}/renouveler - annuel: {}", id, annuel);
|
||||
Abonnement renewed = abonnementService.renouveler(id, annuel);
|
||||
return Response.ok(renewed).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/changer-type")
|
||||
@Authenticated
|
||||
@Operation(summary = "Changer le type d'abonnement (upgrade/downgrade)")
|
||||
@APIResponse(responseCode = "200", description = "Type changé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Type invalide")
|
||||
public Response changerType(
|
||||
@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Nouveau type") @QueryParam("type") @NotNull String type) {
|
||||
logger.info("PUT /abonnements/{}/changer-type - nouveauType: {}", id, type);
|
||||
|
||||
try {
|
||||
TypeAbonnement typeEnum = TypeAbonnement.valueOf(type.toUpperCase());
|
||||
Abonnement updated = abonnementService.changerType(id, typeEnum);
|
||||
return Response.ok(updated).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Type d'abonnement invalide: " + type))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/toggle-auto-renew")
|
||||
@Authenticated
|
||||
@Operation(summary = "Activer/désactiver le renouvellement automatique")
|
||||
@APIResponse(responseCode = "200", description = "Renouvellement automatique modifié")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response toggleAutoRenew(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.info("PUT /abonnements/{}/toggle-auto-renew", id);
|
||||
Abonnement updated = abonnementService.toggleAutoRenouvellement(id);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/annuler")
|
||||
@Authenticated
|
||||
@Operation(summary = "Annuler un abonnement")
|
||||
@APIResponse(responseCode = "204", description = "Abonnement annulé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response annulerAbonnement(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.info("POST /abonnements/{}/annuler", id);
|
||||
abonnementService.annuler(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/suspendre")
|
||||
@Authenticated
|
||||
@Operation(summary = "Suspendre un abonnement")
|
||||
@APIResponse(responseCode = "204", description = "Abonnement suspendu avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response suspendreAbonnement(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.info("POST /abonnements/{}/suspendre", id);
|
||||
abonnementService.suspendre(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reactiver")
|
||||
@Authenticated
|
||||
@Operation(summary = "Réactiver un abonnement suspendu")
|
||||
@APIResponse(responseCode = "204", description = "Abonnement réactivé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response reactiverAbonnement(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.info("POST /abonnements/{}/reactiver", id);
|
||||
abonnementService.reactiver(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE SUPPRESSION ===
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Supprimer définitivement un abonnement")
|
||||
@APIResponse(responseCode = "204", description = "Abonnement supprimé définitivement")
|
||||
@APIResponse(responseCode = "404", description = "Abonnement non trouvé")
|
||||
public Response deleteAbonnement(@Parameter(description = "ID de l'abonnement") @PathParam("id") UUID id) {
|
||||
logger.info("DELETE /abonnements/{}", id);
|
||||
abonnementService.deletePermanently(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.SecurityContext;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
@@ -32,6 +34,52 @@ public class AuthResource {
|
||||
@Inject
|
||||
JsonWebToken jwt;
|
||||
|
||||
/**
|
||||
* Redirige vers Keycloak pour l'authentification
|
||||
* Architecture 2025 : Redirection directe vers https://security.lions.dev pour l'authentification
|
||||
*/
|
||||
@GET
|
||||
@Path("/login")
|
||||
@PermitAll
|
||||
@Operation(
|
||||
summary = "Initier l'authentification Keycloak",
|
||||
description = "Redirige l'utilisateur vers Keycloak (https://security.lions.dev) pour l'authentification OAuth2/OIDC")
|
||||
@APIResponse(responseCode = "302", description = "Redirection vers Keycloak pour authentification")
|
||||
public Response login(@Context SecurityContext securityContext) {
|
||||
try {
|
||||
logger.info("Redirection vers Keycloak pour authentification");
|
||||
|
||||
// Construction de l'URL Keycloak pour l'authentification
|
||||
String keycloakUrl = "https://security.lions.dev/realms/btpxpress/protocol/openid_connect/auth";
|
||||
String clientId = "btpxpress-backend";
|
||||
String redirectUri = "http://localhost:8080/api/v1/auth/callback"; // Peut être configuré dynamiquement
|
||||
String responseType = "code";
|
||||
String scope = "openid profile email";
|
||||
|
||||
// Construction de l'URL complète avec paramètres
|
||||
java.net.URI authUri = java.net.URI.create(
|
||||
String.format(
|
||||
"%s?client_id=%s&redirect_uri=%s&response_type=%s&scope=%s",
|
||||
keycloakUrl,
|
||||
clientId,
|
||||
URLEncoder.encode(redirectUri, StandardCharsets.UTF_8),
|
||||
responseType,
|
||||
URLEncoder.encode(scope, StandardCharsets.UTF_8)
|
||||
)
|
||||
);
|
||||
|
||||
logger.debug("Redirection vers Keycloak: {}", authUri);
|
||||
return Response.status(Response.Status.FOUND)
|
||||
.location(authUri)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la redirection vers Keycloak", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la redirection vers Keycloak", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations de l'utilisateur connecté depuis le token JWT
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.BonCommandeService;
|
||||
import dev.lions.btpxpress.domain.core.entity.BonCommande;
|
||||
import dev.lions.btpxpress.domain.core.entity.PrioriteBonCommande;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutBonCommande;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeBonCommande;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des bons de commande
|
||||
* Architecture 2025 : API complète pour la gestion des bons de commande BTP
|
||||
*/
|
||||
@Path("/api/v1/bons-commande")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Bons de Commande", description = "Gestion des bons de commande BTP")
|
||||
public class BonCommandeResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BonCommandeResource.class);
|
||||
|
||||
@Inject BonCommandeService bonCommandeService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les bons de commande", description = "Retourne la liste complète des bons de commande")
|
||||
@APIResponse(responseCode = "200", description = "Liste des bons de commande récupérée avec succès")
|
||||
public Response getAllBonsCommande() {
|
||||
logger.debug("GET /bons-commande");
|
||||
try {
|
||||
List<BonCommande> bonsCommande = bonCommandeService.findAll();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("bonsCommande", bonsCommande);
|
||||
response.put("total", bonsCommande.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des bons de commande", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des bons de commande", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un bon de commande par ID", description = "Retourne les détails d'un bon de commande")
|
||||
@APIResponse(responseCode = "200", description = "Bon de commande trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
public Response getBonCommandeById(@Parameter(description = "ID du bon de commande") @PathParam("id") UUID id) {
|
||||
logger.debug("GET /bons-commande/{}", id);
|
||||
try {
|
||||
BonCommande bonCommande = bonCommandeService.findById(id);
|
||||
return Response.ok(bonCommande).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du bon de commande: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/numero/{numero}")
|
||||
@Operation(summary = "Récupérer un bon de commande par numéro", description = "Recherche un bon de commande par son numéro")
|
||||
@APIResponse(responseCode = "200", description = "Bon de commande trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
public Response getBonCommandeByNumero(@Parameter(description = "Numéro du bon de commande") @PathParam("numero") String numero) {
|
||||
logger.debug("GET /bons-commande/numero/{}", numero);
|
||||
try {
|
||||
BonCommande bonCommande = bonCommandeService.findByNumero(numero);
|
||||
if (bonCommande == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Bon de commande non trouvé"))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(bonCommande).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du bon de commande par numéro: {}", numero, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(summary = "Récupérer les bons de commande par statut", description = "Filtre les bons de commande par statut")
|
||||
@APIResponse(responseCode = "200", description = "Liste des bons de commande filtrés")
|
||||
public Response getBonsCommandeByStatut(@Parameter(description = "Statut du bon de commande") @PathParam("statut") StatutBonCommande statut) {
|
||||
logger.debug("GET /bons-commande/statut/{}", statut);
|
||||
try {
|
||||
List<BonCommande> bonsCommande = bonCommandeService.findByStatut(statut);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("bonsCommande", bonsCommande);
|
||||
response.put("total", bonsCommande.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des bons de commande par statut: {}", statut, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des bons de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/urgents")
|
||||
@Operation(summary = "Récupérer les bons de commande urgents", description = "Liste les bons de commande prioritaires")
|
||||
@APIResponse(responseCode = "200", description = "Liste des bons de commande urgents")
|
||||
public Response getBonsCommandeUrgents() {
|
||||
logger.debug("GET /bons-commande/urgents");
|
||||
try {
|
||||
List<BonCommande> bonsCommande = bonCommandeService.findUrgents();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("bonsCommande", bonsCommande);
|
||||
response.put("total", bonsCommande.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des bons de commande urgents", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des bons de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Rechercher des bons de commande", description = "Recherche textuelle dans les bons de commande")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
@APIResponse(responseCode = "400", description = "Terme de recherche requis")
|
||||
public Response searchBonsCommande(@Parameter(description = "Terme de recherche") @QueryParam("term") String searchTerm) {
|
||||
logger.debug("GET /bons-commande/search - term: {}", searchTerm);
|
||||
try {
|
||||
if (searchTerm == null || searchTerm.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Terme de recherche requis"))
|
||||
.build();
|
||||
}
|
||||
List<BonCommande> bonsCommande = bonCommandeService.searchCommandes(searchTerm);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("bonsCommande", bonsCommande);
|
||||
response.put("total", bonsCommande.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche de bons de commande: {}", searchTerm, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupérer les statistiques des bons de commande", description = "Retourne des statistiques globales")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
logger.debug("GET /bons-commande/statistiques");
|
||||
try {
|
||||
Map<String, Object> stats = bonCommandeService.getStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@POST
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer un nouveau bon de commande", description = "Crée un nouveau bon de commande")
|
||||
@APIResponse(responseCode = "201", description = "Bon de commande créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createBonCommande(@Valid @NotNull BonCommande bonCommande) {
|
||||
logger.info("POST /bons-commande - Création d'un bon de commande");
|
||||
try {
|
||||
BonCommande nouveauBonCommande = bonCommandeService.create(bonCommande);
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauBonCommande).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du bon de commande", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la création du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MISE À JOUR - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Mettre à jour un bon de commande", description = "Met à jour les informations d'un bon de commande")
|
||||
@APIResponse(responseCode = "200", description = "Bon de commande mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateBonCommande(
|
||||
@Parameter(description = "ID du bon de commande") @PathParam("id") UUID id,
|
||||
@Valid @NotNull BonCommande bonCommandeData) {
|
||||
logger.info("PUT /bons-commande/{} - Mise à jour", id);
|
||||
try {
|
||||
BonCommande bonCommande = bonCommandeService.update(id, bonCommandeData);
|
||||
return Response.ok(bonCommande).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour du bon de commande: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la mise à jour du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/valider")
|
||||
@Authenticated
|
||||
@Operation(summary = "Valider un bon de commande", description = "Valide un bon de commande en attente")
|
||||
@APIResponse(responseCode = "200", description = "Bon de commande validé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Le bon de commande ne peut pas être validé")
|
||||
public Response validerBonCommande(
|
||||
@Parameter(description = "ID du bon de commande") @PathParam("id") UUID id,
|
||||
Map<String, String> payload) {
|
||||
logger.info("POST /bons-commande/{}/valider", id);
|
||||
try {
|
||||
String commentaires = payload != null ? payload.get("commentaires") : null;
|
||||
BonCommande bonCommande = bonCommandeService.validerBonCommande(id, commentaires);
|
||||
return Response.ok(bonCommande).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la validation du bon de commande: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la validation du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/annuler")
|
||||
@Authenticated
|
||||
@Operation(summary = "Annuler un bon de commande", description = "Annule un bon de commande")
|
||||
@APIResponse(responseCode = "200", description = "Bon de commande annulé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Le bon de commande ne peut pas être annulé")
|
||||
public Response annulerBonCommande(
|
||||
@Parameter(description = "ID du bon de commande") @PathParam("id") UUID id,
|
||||
Map<String, String> payload) {
|
||||
logger.info("POST /bons-commande/{}/annuler", id);
|
||||
try {
|
||||
String motif = payload != null ? payload.get("motif") : null;
|
||||
BonCommande bonCommande = bonCommandeService.annulerBonCommande(id, motif);
|
||||
return Response.ok(bonCommande).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'annulation du bon de commande: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'annulation du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE SUPPRESSION - ARCHITECTURE 2025 ===
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Supprimer un bon de commande", description = "Supprime un bon de commande")
|
||||
@APIResponse(responseCode = "204", description = "Bon de commande supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Bon de commande non trouvé")
|
||||
public Response deleteBonCommande(@Parameter(description = "ID du bon de commande") @PathParam("id") UUID id) {
|
||||
logger.info("DELETE /bons-commande/{}", id);
|
||||
try {
|
||||
bonCommandeService.delete(id);
|
||||
return Response.noContent().build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression du bon de commande: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la suppression du bon de commande"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.ComparaisonFournisseurService;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* API REST pour la comparaison des fournisseurs EXPOSITION: Endpoints pour l'aide à la décision et
|
||||
* l'optimisation des achats BTP
|
||||
*/
|
||||
@Path("/api/v1/comparaisons-fournisseurs")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ComparaisonFournisseurResource {
|
||||
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ComparaisonFournisseurResource.class);
|
||||
|
||||
@Inject ComparaisonFournisseurService comparaisonService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public Response findAll(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/ - page: {}, size: {}", page, size);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons;
|
||||
if (page > 0 || size < 1000) {
|
||||
comparaisons = comparaisonService.findAll(page, size);
|
||||
} else {
|
||||
comparaisons = comparaisonService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(comparaisons).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des comparaisons", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response findById(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/{}", id);
|
||||
|
||||
ComparaisonFournisseur comparaison = comparaisonService.findByIdRequired(id);
|
||||
return Response.ok(comparaison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de la comparaison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la comparaison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/materiel/{materielId}")
|
||||
public Response findByMateriel(@PathParam("materielId") UUID materielId) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/materiel/{}", materielId);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons = comparaisonService.findByMateriel(materielId);
|
||||
return Response.ok(comparaisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des comparaisons pour matériel: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/fournisseur/{fournisseurId}")
|
||||
public Response findByFournisseur(@PathParam("fournisseurId") UUID fournisseurId) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/fournisseur/{}", fournisseurId);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons =
|
||||
comparaisonService.findByFournisseur(fournisseurId);
|
||||
return Response.ok(comparaisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des comparaisons pour fournisseur: " + fournisseurId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/session/{sessionId}")
|
||||
public Response findBySession(@PathParam("sessionId") String sessionId) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/session/{}", sessionId);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons = comparaisonService.findBySession(sessionId);
|
||||
return Response.ok(comparaisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des comparaisons pour session: " + sessionId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
public Response search(@QueryParam("terme") String terme) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/search?terme={}", terme);
|
||||
|
||||
List<ComparaisonFournisseur> resultats = comparaisonService.search(terme);
|
||||
return Response.ok(resultats).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche avec terme: " + terme, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS MÉTIER SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/meilleures-offres/{materielId}")
|
||||
public Response findMeilleuresOffres(
|
||||
@PathParam("materielId") UUID materielId,
|
||||
@QueryParam("limite") @DefaultValue("5") int limite) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/meilleures-offres/{}", materielId);
|
||||
|
||||
List<ComparaisonFournisseur> meilleures =
|
||||
comparaisonService.findMeilleuresOffres(materielId, limite);
|
||||
return Response.ok(meilleures).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des meilleures offres: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des meilleures offres: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recommandees")
|
||||
public Response findOffresRecommandees() {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/recommandees");
|
||||
|
||||
List<ComparaisonFournisseur> recommandees = comparaisonService.findOffresRecommandees();
|
||||
return Response.ok(recommandees).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des offres recommandées", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des offres recommandées: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/gamme-prix")
|
||||
public Response findByGammePrix(
|
||||
@QueryParam("prixMin") @NotNull BigDecimal prixMin,
|
||||
@QueryParam("prixMax") @NotNull BigDecimal prixMax) {
|
||||
try {
|
||||
logger.debug(
|
||||
"GET /api/comparaisons-fournisseurs/gamme-prix?prixMin={}&prixMax={}", prixMin, prixMax);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons =
|
||||
comparaisonService.findByGammePrix(prixMin, prixMax);
|
||||
return Response.ok(comparaisons).build();
|
||||
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche par gamme de prix", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche par gamme de prix: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/disponibles-delai")
|
||||
public Response findDisponiblesDansDelai(
|
||||
@QueryParam("maxJours") @DefaultValue("30") int maxJours) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/disponibles-delai?maxJours={}", maxJours);
|
||||
|
||||
List<ComparaisonFournisseur> disponibles =
|
||||
comparaisonService.findDisponiblesDansDelai(maxJours);
|
||||
return Response.ok(disponibles).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche par délai", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche par délai: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION ET GESTION ===
|
||||
|
||||
@POST
|
||||
@Path("/lancer-comparaison")
|
||||
public Response lancerComparaison(@Valid LancerComparaisonRequest request) {
|
||||
try {
|
||||
logger.info("POST /api/comparaisons-fournisseurs/lancer-comparaison");
|
||||
|
||||
String sessionId =
|
||||
comparaisonService.lancerComparaison(
|
||||
request.materielId,
|
||||
request.quantiteDemandee,
|
||||
request.uniteDemandee,
|
||||
request.dateDebutSouhaitee,
|
||||
request.dateFinSouhaitee,
|
||||
request.lieuLivraison,
|
||||
request.evaluateur);
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(Map.of("sessionId", sessionId))
|
||||
.build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du lancement de la comparaison", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du lancement de la comparaison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public Response updateComparaison(
|
||||
@PathParam("id") UUID id, @Valid UpdateComparaisonRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/comparaisons-fournisseurs/{}", id);
|
||||
|
||||
ComparaisonFournisseurService.ComparaisonUpdateRequest updateRequest =
|
||||
mapToServiceRequest(request);
|
||||
|
||||
ComparaisonFournisseur comparaison = comparaisonService.updateComparaison(id, updateRequest);
|
||||
return Response.ok(comparaison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour de la comparaison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de la comparaison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/calculer-scores")
|
||||
public Response calculerScores(@PathParam("id") UUID id, @Valid CalculerScoresRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/comparaisons-fournisseurs/{}/calculer-scores", id);
|
||||
|
||||
ComparaisonFournisseur comparaison = comparaisonService.findByIdRequired(id);
|
||||
comparaisonService.calculerScores(comparaison, request.poidsCriteres);
|
||||
|
||||
return Response.ok(comparaison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du calcul des scores: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du calcul des scores: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/session/{sessionId}/classer")
|
||||
public Response classerComparaisons(@PathParam("sessionId") String sessionId) {
|
||||
try {
|
||||
logger.info("PUT /api/comparaisons-fournisseurs/session/{}/classer", sessionId);
|
||||
|
||||
comparaisonService.classerComparaisons(sessionId);
|
||||
|
||||
List<ComparaisonFournisseur> comparaisons = comparaisonService.findBySession(sessionId);
|
||||
return Response.ok(
|
||||
Map.of("message", "Classement effectué avec succès", "comparaisons", comparaisons))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du classement des comparaisons: " + sessionId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du classement des comparaisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'ANALYSE ET RAPPORTS ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/statistiques");
|
||||
|
||||
Map<String, Object> statistiques = comparaisonService.getStatistiques();
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/evolution-prix/{materielId}")
|
||||
public Response analyserEvolutionPrix(
|
||||
@PathParam("materielId") UUID materielId,
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/evolution-prix/{}", materielId);
|
||||
|
||||
List<Object> evolution =
|
||||
comparaisonService.analyserEvolutionPrix(materielId, dateDebut, dateFin);
|
||||
return Response.ok(evolution).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse d'évolution des prix: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse d'évolution des prix: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/delais-fournisseurs")
|
||||
public Response analyserDelaisFournisseurs() {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/delais-fournisseurs");
|
||||
|
||||
List<Object> delais = comparaisonService.analyserDelaisFournisseurs();
|
||||
return Response.ok(delais).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse des délais fournisseurs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse des délais fournisseurs: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/rapport/{sessionId}")
|
||||
public Response genererRapportComparaison(@PathParam("sessionId") String sessionId) {
|
||||
try {
|
||||
logger.debug("GET /api/comparaisons-fournisseurs/rapport/{}", sessionId);
|
||||
|
||||
Map<String, Object> rapport = comparaisonService.genererRapportComparaison(sessionId);
|
||||
return Response.ok(rapport).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération du rapport: " + sessionId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération du rapport: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS UTILITAIRES ===
|
||||
|
||||
@GET
|
||||
@Path("/criteres-comparaison")
|
||||
public Response getCriteresComparaison() {
|
||||
try {
|
||||
CritereComparaison[] criteres = CritereComparaison.values();
|
||||
|
||||
List<Map<String, Object>> criteresInfo =
|
||||
Arrays.stream(criteres)
|
||||
.map(
|
||||
critere -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("code", critere.name());
|
||||
map.put("libelle", critere.getLibelle());
|
||||
map.put("description", critere.getDescription());
|
||||
map.put("poidsDefaut", critere.getPoidsDefaut());
|
||||
map.put("uniteMesure", critere.getUniteMesure());
|
||||
map.put("icone", critere.getIcone());
|
||||
map.put("couleur", critere.getCouleur());
|
||||
return map;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(criteresInfo).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des critères", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des critères: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
private ComparaisonFournisseurService.ComparaisonUpdateRequest mapToServiceRequest(
|
||||
UpdateComparaisonRequest request) {
|
||||
ComparaisonFournisseurService.ComparaisonUpdateRequest serviceRequest =
|
||||
new ComparaisonFournisseurService.ComparaisonUpdateRequest();
|
||||
|
||||
serviceRequest.disponible = request.disponible;
|
||||
serviceRequest.quantiteDisponible = request.quantiteDisponible;
|
||||
serviceRequest.dateDisponibilite = request.dateDisponibilite;
|
||||
serviceRequest.delaiLivraisonJours = request.delaiLivraisonJours;
|
||||
serviceRequest.prixUnitaireHT = request.prixUnitaireHT;
|
||||
serviceRequest.fraisLivraison = request.fraisLivraison;
|
||||
serviceRequest.fraisInstallation = request.fraisInstallation;
|
||||
serviceRequest.fraisMaintenance = request.fraisMaintenance;
|
||||
serviceRequest.cautionDemandee = request.cautionDemandee;
|
||||
serviceRequest.remiseAppliquee = request.remiseAppliquee;
|
||||
serviceRequest.dureeValiditeOffre = request.dureeValiditeOffre;
|
||||
serviceRequest.delaiPaiement = request.delaiPaiement;
|
||||
serviceRequest.garantieMois = request.garantieMois;
|
||||
serviceRequest.maintenanceIncluse = request.maintenanceIncluse;
|
||||
serviceRequest.formationIncluse = request.formationIncluse;
|
||||
serviceRequest.noteQualite = request.noteQualite;
|
||||
serviceRequest.noteFiabilite = request.noteFiabilite;
|
||||
serviceRequest.distanceKm = request.distanceKm;
|
||||
serviceRequest.conditionsParticulieres = request.conditionsParticulieres;
|
||||
serviceRequest.avantages = request.avantages;
|
||||
serviceRequest.inconvenients = request.inconvenients;
|
||||
serviceRequest.commentairesEvaluateur = request.commentairesEvaluateur;
|
||||
serviceRequest.recommandations = request.recommandations;
|
||||
serviceRequest.poidsCriteres = request.poidsCriteres;
|
||||
|
||||
return serviceRequest;
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class LancerComparaisonRequest {
|
||||
@NotNull public UUID materielId;
|
||||
|
||||
@NotNull public BigDecimal quantiteDemandee;
|
||||
|
||||
public String uniteDemandee;
|
||||
public LocalDate dateDebutSouhaitee;
|
||||
public LocalDate dateFinSouhaitee;
|
||||
public String lieuLivraison;
|
||||
public String evaluateur;
|
||||
}
|
||||
|
||||
public static class UpdateComparaisonRequest {
|
||||
public Boolean disponible;
|
||||
public BigDecimal quantiteDisponible;
|
||||
public LocalDate dateDisponibilite;
|
||||
public Integer delaiLivraisonJours;
|
||||
public BigDecimal prixUnitaireHT;
|
||||
public BigDecimal fraisLivraison;
|
||||
public BigDecimal fraisInstallation;
|
||||
public BigDecimal fraisMaintenance;
|
||||
public BigDecimal cautionDemandee;
|
||||
public BigDecimal remiseAppliquee;
|
||||
public Integer dureeValiditeOffre;
|
||||
public Integer delaiPaiement;
|
||||
public Integer garantieMois;
|
||||
public Boolean maintenanceIncluse;
|
||||
public Boolean formationIncluse;
|
||||
public BigDecimal noteQualite;
|
||||
public BigDecimal noteFiabilite;
|
||||
public BigDecimal distanceKm;
|
||||
public String conditionsParticulieres;
|
||||
public String avantages;
|
||||
public String inconvenients;
|
||||
public String commentairesEvaluateur;
|
||||
public String recommandations;
|
||||
public Map<CritereComparaison, Integer> poidsCriteres;
|
||||
}
|
||||
|
||||
public static class CalculerScoresRequest {
|
||||
public Map<CritereComparaison, Integer> poidsCriteres;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.EntrepriseProfileService;
|
||||
import dev.lions.btpxpress.domain.core.entity.EntrepriseProfile;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeAbonnement;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des profils d'entreprise
|
||||
* Architecture 2025 : API complète pour la gestion des profils d'entreprise et leurs notations
|
||||
*/
|
||||
@Path("/api/v1/entreprise-profiles")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Profils d'Entreprise", description = "Gestion des profils d'entreprise")
|
||||
public class EntrepriseProfileResource {
|
||||
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(EntrepriseProfileResource.class);
|
||||
|
||||
@Inject EntrepriseProfileService entrepriseProfileService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Récupérer tous les profils d'entreprise visibles",
|
||||
description = "Retourne la liste complète des profils d'entreprise visibles, triés par note")
|
||||
@APIResponse(responseCode = "200", description = "Liste des profils récupérée avec succès")
|
||||
public Response getAllProfiles(
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
|
||||
int size) {
|
||||
logger.debug("GET /entreprise-profiles - page: {}, size: {}", page, size);
|
||||
|
||||
List<EntrepriseProfile> profiles;
|
||||
if (page == 0 && size == 20) {
|
||||
profiles = entrepriseProfileService.findAll();
|
||||
} else {
|
||||
profiles = entrepriseProfileService.findAll(page, size);
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("profiles", profiles);
|
||||
response.put("total", profiles.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer un profil d'entreprise par ID",
|
||||
description = "Retourne les détails complets d'un profil d'entreprise")
|
||||
@APIResponse(responseCode = "200", description = "Profil trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response getProfileById(@Parameter(description = "ID du profil") @PathParam("id") UUID id) {
|
||||
logger.debug("GET /entreprise-profiles/{}", id);
|
||||
|
||||
EntrepriseProfile profile = entrepriseProfileService.findByIdRequired(id);
|
||||
return Response.ok(profile).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(
|
||||
summary = "Rechercher des profils d'entreprise",
|
||||
description = "Recherche textuelle complète dans les profils")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
public Response searchProfiles(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String searchTerm,
|
||||
@Parameter(description = "Zone d'intervention") @QueryParam("zone") String zone,
|
||||
@Parameter(description = "Spécialité") @QueryParam("specialite") String specialite,
|
||||
@Parameter(description = "Région") @QueryParam("region") String region,
|
||||
@Parameter(description = "Ville") @QueryParam("ville") String ville,
|
||||
@Parameter(description = "Certifié uniquement") @QueryParam("certifie") Boolean certifie,
|
||||
@Parameter(description = "Type d'abonnement") @QueryParam("typeAbonnement")
|
||||
TypeAbonnement typeAbonnement) {
|
||||
logger.debug(
|
||||
"GET /entreprise-profiles/search - q: {}, zone: {}, specialite: {}, region: {}, ville: {}, certifie: {}",
|
||||
searchTerm,
|
||||
zone,
|
||||
specialite,
|
||||
region,
|
||||
ville,
|
||||
certifie);
|
||||
|
||||
List<EntrepriseProfile> profiles;
|
||||
|
||||
// Recherche par terme de recherche complet
|
||||
if (searchTerm != null && !searchTerm.trim().isEmpty()) {
|
||||
profiles = entrepriseProfileService.searchFullText(searchTerm);
|
||||
}
|
||||
// Recherche par zone d'intervention
|
||||
else if (zone != null && !zone.trim().isEmpty()) {
|
||||
profiles = entrepriseProfileService.findByZoneIntervention(zone);
|
||||
}
|
||||
// Recherche par spécialité
|
||||
else if (specialite != null && !specialite.trim().isEmpty()) {
|
||||
profiles = entrepriseProfileService.findBySpecialite(specialite);
|
||||
}
|
||||
// Recherche par région
|
||||
else if (region != null && !region.trim().isEmpty()) {
|
||||
profiles = entrepriseProfileService.findByRegion(region);
|
||||
}
|
||||
// Recherche par ville
|
||||
else if (ville != null && !ville.trim().isEmpty()) {
|
||||
profiles = entrepriseProfileService.findByVille(ville);
|
||||
}
|
||||
// Recherche par certification
|
||||
else if (certifie != null) {
|
||||
profiles = entrepriseProfileService.findByCertifie(certifie);
|
||||
}
|
||||
// Recherche par type d'abonnement
|
||||
else if (typeAbonnement != null) {
|
||||
profiles = entrepriseProfileService.findByTypeAbonnement(typeAbonnement);
|
||||
}
|
||||
// Par défaut, retourner tous les profils
|
||||
else {
|
||||
profiles = entrepriseProfileService.findAll();
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("profiles", profiles);
|
||||
response.put("total", profiles.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/top-rated")
|
||||
@Operation(
|
||||
summary = "Récupérer les profils les mieux notés",
|
||||
description = "Retourne les profils d'entreprise avec les meilleures notes")
|
||||
@APIResponse(responseCode = "200", description = "Liste des profils les mieux notés")
|
||||
public Response getTopRated(
|
||||
@Parameter(description = "Nombre de profils à retourner") @QueryParam("limit")
|
||||
@DefaultValue("10")
|
||||
int limit) {
|
||||
logger.debug("GET /entreprise-profiles/top-rated - limit: {}", limit);
|
||||
|
||||
List<EntrepriseProfile> profiles = entrepriseProfileService.findTopRated(limit);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("profiles", profiles);
|
||||
response.put("total", profiles.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistics")
|
||||
@Operation(
|
||||
summary = "Récupérer les statistiques des profils",
|
||||
description = "Retourne des statistiques globales sur les profils d'entreprise")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistics() {
|
||||
logger.debug("GET /entreprise-profiles/statistics");
|
||||
|
||||
Map<String, Object> stats = entrepriseProfileService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/user/{userId}")
|
||||
@Operation(
|
||||
summary = "Récupérer le profil d'un utilisateur",
|
||||
description = "Retourne le profil d'entreprise associé à un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Profil trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé pour cet utilisateur")
|
||||
public Response getProfileByUserId(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") UUID userId) {
|
||||
logger.debug("GET /entreprise-profiles/user/{}", userId);
|
||||
|
||||
return entrepriseProfileService
|
||||
.findByUserId(userId)
|
||||
.map(profile -> Response.ok(profile).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Profil non trouvé pour cet utilisateur"))
|
||||
.build());
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@POST
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Créer un nouveau profil d'entreprise",
|
||||
description = "Crée un nouveau profil d'entreprise pour un utilisateur")
|
||||
@APIResponse(responseCode = "201", description = "Profil créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Un profil existe déjà pour cet utilisateur")
|
||||
public Response createProfile(@Valid @NotNull EntrepriseProfile profile) {
|
||||
logger.info("POST /entreprise-profiles - Création d'un profil: {}", profile.getNomCommercial());
|
||||
|
||||
EntrepriseProfile created = entrepriseProfileService.create(profile);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MISE À JOUR - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Mettre à jour un profil d'entreprise",
|
||||
description = "Met à jour les informations d'un profil d'entreprise existant")
|
||||
@APIResponse(responseCode = "200", description = "Profil mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateProfile(
|
||||
@Parameter(description = "ID du profil") @PathParam("id") UUID id,
|
||||
@Valid @NotNull EntrepriseProfile profileUpdate) {
|
||||
logger.info("PUT /entreprise-profiles/{} - Mise à jour", id);
|
||||
|
||||
EntrepriseProfile updated = entrepriseProfileService.update(id, profileUpdate);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/note")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Mettre à jour la note d'un profil",
|
||||
description = "Met à jour la note globale et le nombre d'avis d'un profil")
|
||||
@APIResponse(responseCode = "200", description = "Note mise à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response updateNote(
|
||||
@Parameter(description = "ID du profil") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Nouvelle note globale") @QueryParam("note")
|
||||
@NotNull
|
||||
BigDecimal note,
|
||||
@Parameter(description = "Nouveau nombre d'avis") @QueryParam("nombreAvis")
|
||||
@DefaultValue("0")
|
||||
int nombreAvis) {
|
||||
logger.info("PUT /entreprise-profiles/{}/note - note: {}, nombreAvis: {}", id, note, nombreAvis);
|
||||
|
||||
EntrepriseProfile updated = entrepriseProfileService.updateNote(id, note, nombreAvis);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/increment-projects")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Incrémenter le nombre de projets réalisés",
|
||||
description = "Incrémente le compteur de projets réalisés pour un profil")
|
||||
@APIResponse(responseCode = "200", description = "Compteur incrémenté avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response incrementProjects(
|
||||
@Parameter(description = "ID du profil") @PathParam("id") UUID id) {
|
||||
logger.debug("PUT /entreprise-profiles/{}/increment-projects", id);
|
||||
|
||||
EntrepriseProfile updated = entrepriseProfileService.incrementerProjets(id);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/increment-clients")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Incrémenter le nombre de clients servis",
|
||||
description = "Incrémente le compteur de clients servis pour un profil")
|
||||
@APIResponse(responseCode = "200", description = "Compteur incrémenté avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response incrementClients(@Parameter(description = "ID du profil") @PathParam("id") UUID id) {
|
||||
logger.debug("PUT /entreprise-profiles/{}/increment-clients", id);
|
||||
|
||||
EntrepriseProfile updated = entrepriseProfileService.incrementerClients(id);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE SUPPRESSION - ARCHITECTURE 2025 ===
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Supprimer un profil d'entreprise",
|
||||
description = "Supprime (désactive) un profil d'entreprise")
|
||||
@APIResponse(responseCode = "204", description = "Profil supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response deleteProfile(@Parameter(description = "ID du profil") @PathParam("id") UUID id) {
|
||||
logger.info("DELETE /entreprise-profiles/{}", id);
|
||||
|
||||
entrepriseProfileService.delete(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}/permanent")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Supprimer définitivement un profil",
|
||||
description = "Supprime définitivement un profil d'entreprise de la base de données")
|
||||
@APIResponse(responseCode = "204", description = "Profil supprimé définitivement")
|
||||
@APIResponse(responseCode = "404", description = "Profil non trouvé")
|
||||
public Response deleteProfilePermanently(
|
||||
@Parameter(description = "ID du profil") @PathParam("id") UUID id) {
|
||||
logger.info("DELETE /entreprise-profiles/{}/permanent", id);
|
||||
|
||||
entrepriseProfileService.deletePermanently(id);
|
||||
return Response.status(Response.Status.NO_CONTENT).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.FournisseurService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Fournisseur;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des fournisseurs BTP
|
||||
* Architecture 2025 : API complète pour la gestion des fournisseurs
|
||||
*/
|
||||
@Path("/api/v1/fournisseurs")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Fournisseurs", description = "Gestion des fournisseurs BTP")
|
||||
public class FournisseurResource {
|
||||
|
||||
@Inject
|
||||
FournisseurService fournisseurService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupère tous les fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des fournisseurs")
|
||||
public Response getAllFournisseurs(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("10") int size,
|
||||
@QueryParam("search") String search) {
|
||||
try {
|
||||
List<Fournisseur> fournisseurs;
|
||||
if (search != null && !search.trim().isEmpty()) {
|
||||
fournisseurs = fournisseurService.searchFournisseurs(search);
|
||||
} else {
|
||||
fournisseurs = fournisseurService.getAllFournisseurs(page, size);
|
||||
}
|
||||
return Response.ok(fournisseurs).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des fournisseurs"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère un fournisseur par ID")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response getFournisseurById(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
Fournisseur fournisseur = fournisseurService.getFournisseurById(id);
|
||||
return Response.ok(fournisseur).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Recherche des fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
|
||||
public Response searchFournisseurs(@QueryParam("q") String query) {
|
||||
try {
|
||||
List<Fournisseur> fournisseurs = fournisseurService.searchFournisseurs(query);
|
||||
return Response.ok(fournisseurs).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Récupère les statistiques des fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques des fournisseurs")
|
||||
public Response getFournisseurStats() {
|
||||
try {
|
||||
Map<String, Object> stats = fournisseurService.getFournisseurStats();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du calcul des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Crée un nouveau fournisseur")
|
||||
@APIResponse(responseCode = "201", description = "Fournisseur créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - fournisseur existant")
|
||||
public Response createFournisseur(@Valid Fournisseur fournisseur) {
|
||||
try {
|
||||
Fournisseur created = fournisseurService.createFournisseur(fournisseur);
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(created)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Erreur lors de la création du fournisseur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour un fournisseur existant")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response updateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id,
|
||||
@Valid Fournisseur fournisseur) {
|
||||
try {
|
||||
Fournisseur updated = fournisseurService.updateFournisseur(id, fournisseur);
|
||||
return Response.ok(updated).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime un fournisseur")
|
||||
@APIResponse(responseCode = "204", description = "Fournisseur supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response deleteFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.deleteFournisseur(id);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MODIFICATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activate")
|
||||
@Operation(summary = "Active un fournisseur")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur activé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response activateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.activateFournisseur(id);
|
||||
return Response.ok(Map.of("message", "Fournisseur activé avec succès")).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
@Operation(summary = "Désactive un fournisseur")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur désactivé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response deactivateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.deactivateFournisseur(id);
|
||||
return Response.ok(Map.of("message", "Fournisseur désactivé avec succès")).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,849 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.LivraisonMaterielService;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des livraisons de matériel
|
||||
* Architecture 2025 : API complète pour la logistique et le suivi des livraisons BTP
|
||||
*/
|
||||
@Path("/api/v1/livraisons-materiel")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class LivraisonMaterielResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LivraisonMaterielResource.class);
|
||||
|
||||
@Inject LivraisonMaterielService livraisonService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public Response findAll(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/ - page: {}, size: {}", page, size);
|
||||
|
||||
List<LivraisonMateriel> livraisons;
|
||||
if (page > 0 || size < 1000) {
|
||||
livraisons = livraisonService.findAll(page, size);
|
||||
} else {
|
||||
livraisons = livraisonService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(livraisons).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response findById(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/{}", id);
|
||||
|
||||
LivraisonMateriel livraison = livraisonService.findByIdRequired(id);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de la livraison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/numero/{numero}")
|
||||
public Response findByNumero(@PathParam("numero") String numeroLivraison) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/numero/{}", numeroLivraison);
|
||||
|
||||
return livraisonService
|
||||
.findByNumero(numeroLivraison)
|
||||
.map(livraison -> Response.ok(livraison).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Livraison non trouvée avec le numéro: " + numeroLivraison)
|
||||
.build());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération de la livraison par numéro: " + numeroLivraison, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reservation/{reservationId}")
|
||||
public Response findByReservation(@PathParam("reservationId") UUID reservationId) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/reservation/{}", reservationId);
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findByReservation(reservationId);
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des livraisons pour réservation: " + reservationId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
public Response findByChantier(@PathParam("chantierId") UUID chantierId) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/chantier/{}", chantierId);
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findByChantier(chantierId);
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons pour chantier: " + chantierId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
public Response findByStatut(@PathParam("statut") String statutStr) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/statut/{}", statutStr);
|
||||
|
||||
StatutLivraison statut = StatutLivraison.fromString(statutStr);
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findByStatut(statut);
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons par statut: " + statutStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/transporteur/{transporteur}")
|
||||
public Response findByTransporteur(@PathParam("transporteur") String transporteur) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/transporteur/{}", transporteur);
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findByTransporteur(transporteur);
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des livraisons pour transporteur: " + transporteur, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
public Response search(@QueryParam("terme") String terme) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/search?terme={}", terme);
|
||||
|
||||
List<LivraisonMateriel> resultats = livraisonService.search(terme);
|
||||
return Response.ok(resultats).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche avec terme: " + terme, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS MÉTIER SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/du-jour")
|
||||
public Response findLivraisonsDuJour() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/du-jour");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findLivraisonsDuJour();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons du jour", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-cours")
|
||||
public Response findLivraisonsEnCours() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/en-cours");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findLivraisonsEnCours();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons en cours", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
public Response findLivraisonsEnRetard() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/en-retard");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findLivraisonsEnRetard();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/avec-incidents")
|
||||
public Response findAvecIncidents() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/avec-incidents");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findAvecIncidents();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons avec incidents", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/prioritaires")
|
||||
public Response findLivraisonsPrioritaires() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/prioritaires");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findLivraisonsPrioritaires();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons prioritaires", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tracking-actif")
|
||||
public Response findAvecTrackingActif() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/tracking-actif");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findAvecTrackingActif();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons avec tracking", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/necessitant-action")
|
||||
public Response findNecessitantAction() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/necessitant-action");
|
||||
|
||||
List<LivraisonMateriel> livraisons = livraisonService.findNecessitantAction();
|
||||
return Response.ok(livraisons).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des livraisons nécessitant action", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des livraisons: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION ET MODIFICATION ===
|
||||
|
||||
@POST
|
||||
@Path("/")
|
||||
public Response creerLivraison(@Valid CreerLivraisonRequest request) {
|
||||
try {
|
||||
logger.info("POST /api/livraisons-materiel/ - création livraison");
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.creerLivraison(
|
||||
request.reservationId,
|
||||
request.typeTransport,
|
||||
request.dateLivraisonPrevue,
|
||||
request.heureLivraisonPrevue,
|
||||
request.transporteur,
|
||||
request.planificateur);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création de la livraison", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de la livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public Response updateLivraison(@PathParam("id") UUID id, @Valid UpdateLivraisonRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}", id);
|
||||
|
||||
LivraisonMaterielService.LivraisonUpdateRequest updateRequest = mapToServiceRequest(request);
|
||||
LivraisonMateriel livraison = livraisonService.updateLivraison(id, updateRequest);
|
||||
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour de la livraison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de la livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION DU WORKFLOW ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/demarrer-preparation")
|
||||
public Response demarrerPreparation(@PathParam("id") UUID id, @Valid OperateurRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/demarrer-preparation", id);
|
||||
|
||||
LivraisonMateriel livraison = livraisonService.demarrerPreparation(id, request.operateur);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du démarrage de la préparation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du démarrage de la préparation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/marquer-prete")
|
||||
public Response marquerPrete(@PathParam("id") UUID id, @Valid MarquerPreteRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/marquer-prete", id);
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.marquerPrete(id, request.operateur, request.observations);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du marquage prêt: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du marquage prêt: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/demarrer-transit")
|
||||
public Response demarrerTransit(@PathParam("id") UUID id, @Valid DemarrerTransitRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/demarrer-transit", id);
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.demarrerTransit(id, request.chauffeur, request.heureDepart);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du démarrage du transit: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du démarrage du transit: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/signaler-arrivee")
|
||||
public Response signalerArrivee(@PathParam("id") UUID id, @Valid SignalerArriveeRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/signaler-arrivee", id);
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.signalerArrivee(
|
||||
id, request.chauffeur, request.heureArrivee, request.latitude, request.longitude);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du signalement d'arrivée: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du signalement d'arrivée: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/commencer-dechargement")
|
||||
public Response commencerDechargement(@PathParam("id") UUID id, @Valid OperateurRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/commencer-dechargement", id);
|
||||
|
||||
LivraisonMateriel livraison = livraisonService.commencerDechargement(id, request.operateur);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du début de déchargement: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du début de déchargement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/finaliser")
|
||||
public Response finaliserLivraison(@PathParam("id") UUID id, @Valid FinalisationRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/finaliser", id);
|
||||
|
||||
LivraisonMaterielService.FinalisationLivraisonRequest finalisationRequest =
|
||||
mapToFinalisationRequest(request);
|
||||
|
||||
LivraisonMateriel livraison = livraisonService.finaliserLivraison(id, finalisationRequest);
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la finalisation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la finalisation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/signaler-incident")
|
||||
public Response signalerIncident(
|
||||
@PathParam("id") UUID id, @Valid SignalerIncidentRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/signaler-incident", id);
|
||||
|
||||
LivraisonMaterielService.IncidentRequest incidentRequest = mapToIncidentRequest(request);
|
||||
LivraisonMateriel livraison = livraisonService.signalerIncident(id, incidentRequest);
|
||||
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du signalement d'incident: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du signalement d'incident: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/retarder")
|
||||
public Response retarderLivraison(
|
||||
@PathParam("id") UUID id, @Valid RetarderLivraisonRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/retarder", id);
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.retarderLivraison(
|
||||
id,
|
||||
request.nouvelleDatePrevue,
|
||||
request.nouvelleHeurePrevue,
|
||||
request.motif,
|
||||
request.operateur);
|
||||
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du retard de livraison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du retard de livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/annuler")
|
||||
public Response annulerLivraison(
|
||||
@PathParam("id") UUID id, @Valid AnnulerLivraisonRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/livraisons-materiel/{}/annuler", id);
|
||||
|
||||
LivraisonMateriel livraison =
|
||||
livraisonService.annulerLivraison(id, request.motifAnnulation, request.operateur);
|
||||
|
||||
return Response.ok(livraison).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'annulation de livraison: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'annulation de livraison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE SUIVI ET TRACKING ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/position-gps")
|
||||
public Response mettreAJourPositionGPS(
|
||||
@PathParam("id") UUID id, @Valid PositionGPSRequest request) {
|
||||
try {
|
||||
livraisonService.mettreAJourPositionGPS(
|
||||
id, request.latitude, request.longitude, request.vitesseKmh);
|
||||
return Response.ok(Map.of("message", "Position mise à jour")).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour GPS: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour GPS: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/eta")
|
||||
public Response calculerETA(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/{}/eta", id);
|
||||
|
||||
Map<String, Object> eta = livraisonService.calculerETA(id);
|
||||
return Response.ok(eta).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du calcul ETA: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du calcul ETA: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'OPTIMISATION ===
|
||||
|
||||
@POST
|
||||
@Path("/optimiser-itineraires")
|
||||
public Response optimiserItineraires(@Valid OptimiserItinerairesRequest request) {
|
||||
try {
|
||||
logger.info("POST /api/livraisons-materiel/optimiser-itineraires");
|
||||
|
||||
List<LivraisonMateriel> itineraireOptimise =
|
||||
livraisonService.optimiserItineraires(request.date, request.transporteur);
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"itineraireOptimise",
|
||||
itineraireOptimise,
|
||||
"nombreLivraisons",
|
||||
itineraireOptimise.size()))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'optimisation des itinéraires", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'optimisation des itinéraires: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/statistiques");
|
||||
|
||||
Map<String, Object> statistiques = livraisonService.getStatistiques();
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tableau-bord")
|
||||
public Response getTableauBordLogistique() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/tableau-bord");
|
||||
|
||||
Map<String, Object> tableauBord = livraisonService.getTableauBordLogistique();
|
||||
return Response.ok(tableauBord).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération du tableau de bord", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération du tableau de bord: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/performance-transporteurs")
|
||||
public Response analyserPerformanceTransporteurs() {
|
||||
try {
|
||||
logger.debug("GET /api/livraisons-materiel/performance-transporteurs");
|
||||
|
||||
List<Object> performance = livraisonService.analyserPerformanceTransporteurs();
|
||||
return Response.ok(performance).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse des performances", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse des performances: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
private LivraisonMaterielService.LivraisonUpdateRequest mapToServiceRequest(
|
||||
UpdateLivraisonRequest request) {
|
||||
LivraisonMaterielService.LivraisonUpdateRequest serviceRequest =
|
||||
new LivraisonMaterielService.LivraisonUpdateRequest();
|
||||
|
||||
serviceRequest.dateLivraisonPrevue = request.dateLivraisonPrevue;
|
||||
serviceRequest.heureLivraisonPrevue = request.heureLivraisonPrevue;
|
||||
serviceRequest.transporteur = request.transporteur;
|
||||
serviceRequest.chauffeur = request.chauffeur;
|
||||
serviceRequest.telephoneChauffeur = request.telephoneChauffeur;
|
||||
serviceRequest.immatriculation = request.immatriculation;
|
||||
serviceRequest.contactReception = request.contactReception;
|
||||
serviceRequest.telephoneContact = request.telephoneContact;
|
||||
serviceRequest.instructionsSpeciales = request.instructionsSpeciales;
|
||||
serviceRequest.accesChantier = request.accesChantier;
|
||||
serviceRequest.modifiePar = request.modifiePar;
|
||||
|
||||
return serviceRequest;
|
||||
}
|
||||
|
||||
private LivraisonMaterielService.FinalisationLivraisonRequest mapToFinalisationRequest(
|
||||
FinalisationRequest request) {
|
||||
LivraisonMaterielService.FinalisationLivraisonRequest finalisationRequest =
|
||||
new LivraisonMaterielService.FinalisationLivraisonRequest();
|
||||
|
||||
finalisationRequest.quantiteLivree = request.quantiteLivree;
|
||||
finalisationRequest.etatMateriel = request.etatMateriel;
|
||||
finalisationRequest.observations = request.observations;
|
||||
finalisationRequest.receptionnaire = request.receptionnaire;
|
||||
finalisationRequest.conforme = request.conforme;
|
||||
finalisationRequest.photoLivraison = request.photoLivraison;
|
||||
|
||||
return finalisationRequest;
|
||||
}
|
||||
|
||||
private LivraisonMaterielService.IncidentRequest mapToIncidentRequest(
|
||||
SignalerIncidentRequest request) {
|
||||
LivraisonMaterielService.IncidentRequest incidentRequest =
|
||||
new LivraisonMaterielService.IncidentRequest();
|
||||
|
||||
incidentRequest.typeIncident = request.typeIncident;
|
||||
incidentRequest.description = request.description;
|
||||
incidentRequest.impact = request.impact;
|
||||
incidentRequest.actionsCorrectives = request.actionsCorrectives;
|
||||
incidentRequest.declarant = request.declarant;
|
||||
|
||||
return incidentRequest;
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreerLivraisonRequest {
|
||||
@NotNull public UUID reservationId;
|
||||
|
||||
@NotNull public TypeTransport typeTransport;
|
||||
|
||||
@NotNull public LocalDate dateLivraisonPrevue;
|
||||
|
||||
public LocalTime heureLivraisonPrevue;
|
||||
public String transporteur;
|
||||
public String planificateur;
|
||||
}
|
||||
|
||||
public static class UpdateLivraisonRequest {
|
||||
public LocalDate dateLivraisonPrevue;
|
||||
public LocalTime heureLivraisonPrevue;
|
||||
public String transporteur;
|
||||
public String chauffeur;
|
||||
public String telephoneChauffeur;
|
||||
public String immatriculation;
|
||||
public String contactReception;
|
||||
public String telephoneContact;
|
||||
public String instructionsSpeciales;
|
||||
public String accesChantier;
|
||||
public String modifiePar;
|
||||
}
|
||||
|
||||
public static class OperateurRequest {
|
||||
@NotNull public String operateur;
|
||||
}
|
||||
|
||||
public static class MarquerPreteRequest {
|
||||
@NotNull public String operateur;
|
||||
|
||||
public String observations;
|
||||
}
|
||||
|
||||
public static class DemarrerTransitRequest {
|
||||
@NotNull public String chauffeur;
|
||||
|
||||
public LocalTime heureDepart;
|
||||
}
|
||||
|
||||
public static class SignalerArriveeRequest {
|
||||
@NotNull public String chauffeur;
|
||||
|
||||
public LocalTime heureArrivee;
|
||||
public BigDecimal latitude;
|
||||
public BigDecimal longitude;
|
||||
}
|
||||
|
||||
public static class FinalisationRequest {
|
||||
@NotNull public BigDecimal quantiteLivree;
|
||||
|
||||
public String etatMateriel;
|
||||
public String observations;
|
||||
|
||||
@NotNull public String receptionnaire;
|
||||
|
||||
public Boolean conforme;
|
||||
public String photoLivraison;
|
||||
}
|
||||
|
||||
public static class SignalerIncidentRequest {
|
||||
@NotNull public String typeIncident;
|
||||
|
||||
@NotNull public String description;
|
||||
|
||||
public String impact;
|
||||
public String actionsCorrectives;
|
||||
|
||||
@NotNull public String declarant;
|
||||
}
|
||||
|
||||
public static class RetarderLivraisonRequest {
|
||||
@NotNull public LocalDate nouvelleDatePrevue;
|
||||
|
||||
public LocalTime nouvelleHeurePrevue;
|
||||
|
||||
@NotNull public String motif;
|
||||
|
||||
@NotNull public String operateur;
|
||||
}
|
||||
|
||||
public static class AnnulerLivraisonRequest {
|
||||
@NotNull public String motifAnnulation;
|
||||
|
||||
@NotNull public String operateur;
|
||||
}
|
||||
|
||||
public static class PositionGPSRequest {
|
||||
@NotNull public BigDecimal latitude;
|
||||
|
||||
@NotNull public BigDecimal longitude;
|
||||
|
||||
public Integer vitesseKmh;
|
||||
}
|
||||
|
||||
public static class OptimiserItinerairesRequest {
|
||||
@NotNull public LocalDate date;
|
||||
|
||||
public String transporteur;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.PermissionService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Permission;
|
||||
import dev.lions.btpxpress.domain.core.entity.Permission.PermissionCategory;
|
||||
import dev.lions.btpxpress.domain.core.entity.UserRole;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des permissions EXPOSITION: Consultation des droits d'accès et
|
||||
* permissions par rôle
|
||||
*/
|
||||
@Path("/api/permissions")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class PermissionResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PermissionResource.class);
|
||||
|
||||
@Inject PermissionService permissionService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
/** Récupère toutes les permissions disponibles */
|
||||
@GET
|
||||
@Path("/all")
|
||||
public Response getAllPermissions() {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/all");
|
||||
|
||||
List<Object> permissions =
|
||||
Arrays.stream(Permission.values())
|
||||
.map(
|
||||
p ->
|
||||
Map.of(
|
||||
"code", p.getCode(),
|
||||
"description", p.getDescription(),
|
||||
"category", p.getCategory().name(),
|
||||
"categoryDisplay", p.getCategory().getDisplayName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(permissions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des permissions", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les permissions par catégorie */
|
||||
@GET
|
||||
@Path("/categories")
|
||||
public Response getPermissionsByCategory() {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/categories");
|
||||
|
||||
Map<String, Object> result =
|
||||
Arrays.stream(PermissionCategory.values())
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
category -> category.name(),
|
||||
category ->
|
||||
Map.of(
|
||||
"displayName", category.getDisplayName(),
|
||||
"permissions",
|
||||
Permission.getByCategory(category).stream()
|
||||
.map(
|
||||
p ->
|
||||
Map.of(
|
||||
"code", p.getCode(),
|
||||
"description", p.getDescription()))
|
||||
.collect(Collectors.toList()))));
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des permissions par catégorie", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les permissions d'un rôle spécifique */
|
||||
@GET
|
||||
@Path("/role/{role}")
|
||||
public Response getPermissionsByRole(@PathParam("role") String roleStr) {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/role/{}", roleStr);
|
||||
|
||||
UserRole role = UserRole.valueOf(roleStr.toUpperCase());
|
||||
Map<String, Object> summary = permissionService.getPermissionSummary(role);
|
||||
|
||||
return Response.ok(summary).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Rôle invalide: " + roleStr)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des permissions pour le rôle: " + roleStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Vérifie si un rôle a une permission spécifique */
|
||||
@GET
|
||||
@Path("/check/{role}/{permission}")
|
||||
public Response checkPermission(
|
||||
@PathParam("role") String roleStr, @PathParam("permission") String permissionCode) {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/check/{}/{}", roleStr, permissionCode);
|
||||
|
||||
UserRole role = UserRole.valueOf(roleStr.toUpperCase());
|
||||
boolean hasPermission = permissionService.hasPermission(role, permissionCode);
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"role", role.getDisplayName(),
|
||||
"permission", permissionCode,
|
||||
"hasPermission", hasPermission))
|
||||
.build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Rôle invalide: " + roleStr)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la vérification de permission", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la vérification de permission: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère tous les rôles avec leurs permissions */
|
||||
@GET
|
||||
@Path("/roles")
|
||||
public Response getAllRolesWithPermissions() {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/roles");
|
||||
|
||||
Map<String, Object> result =
|
||||
Arrays.stream(UserRole.values())
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
role -> role.name(),
|
||||
role ->
|
||||
Map.of(
|
||||
"displayName", role.getDisplayName(),
|
||||
"description", role.getDescription(),
|
||||
"hierarchyLevel", role.getHierarchyLevel(),
|
||||
"isManagementRole", role.isManagementRole(),
|
||||
"isFieldRole", role.isFieldRole(),
|
||||
"isAdministrativeRole", role.isAdministrativeRole(),
|
||||
"permissions",
|
||||
permissionService.getPermissions(role).stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList()),
|
||||
"permissionCount", permissionService.getPermissions(role).size())));
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des rôles et permissions", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des rôles: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Compare les permissions entre deux rôles */
|
||||
@GET
|
||||
@Path("/compare/{role1}/{role2}")
|
||||
public Response compareRoles(
|
||||
@PathParam("role1") String role1Str, @PathParam("role2") String role2Str) {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/compare/{}/{}", role1Str, role2Str);
|
||||
|
||||
UserRole role1 = UserRole.valueOf(role1Str.toUpperCase());
|
||||
UserRole role2 = UserRole.valueOf(role2Str.toUpperCase());
|
||||
|
||||
Set<Permission> permissions1 = permissionService.getPermissions(role1);
|
||||
Set<Permission> permissions2 = permissionService.getPermissions(role2);
|
||||
Set<Permission> missing1to2 = permissionService.getMissingPermissions(role1, role2);
|
||||
Set<Permission> missing2to1 = permissionService.getMissingPermissions(role2, role1);
|
||||
|
||||
Set<Permission> common =
|
||||
permissions1.stream().filter(permissions2::contains).collect(Collectors.toSet());
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"role1",
|
||||
Map.of(
|
||||
"name", role1.getDisplayName(),
|
||||
"permissionCount", permissions1.size(),
|
||||
"permissions",
|
||||
permissions1.stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList())),
|
||||
"role2",
|
||||
Map.of(
|
||||
"name", role2.getDisplayName(),
|
||||
"permissionCount", permissions2.size(),
|
||||
"permissions",
|
||||
permissions2.stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList())),
|
||||
"common",
|
||||
Map.of(
|
||||
"count", common.size(),
|
||||
"permissions",
|
||||
common.stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList())),
|
||||
"onlyInRole1",
|
||||
Map.of(
|
||||
"count", missing2to1.size(),
|
||||
"permissions",
|
||||
missing2to1.stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList())),
|
||||
"onlyInRole2",
|
||||
Map.of(
|
||||
"count", missing1to2.size(),
|
||||
"permissions",
|
||||
missing1to2.stream()
|
||||
.map(Permission::getCode)
|
||||
.collect(Collectors.toList()))))
|
||||
.build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("Rôle invalide").build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la comparaison des rôles", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la comparaison: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les permissions spécifiques au gestionnaire de projet */
|
||||
@GET
|
||||
@Path("/gestionnaire")
|
||||
public Response getGestionnairePermissions() {
|
||||
try {
|
||||
logger.debug("GET /api/permissions/gestionnaire");
|
||||
|
||||
UserRole gestionnaireRole = UserRole.GESTIONNAIRE_PROJET;
|
||||
Map<String, Object> summary = permissionService.getPermissionSummary(gestionnaireRole);
|
||||
|
||||
// Ajout d'informations spécifiques au gestionnaire
|
||||
Map<PermissionCategory, List<Permission>> byCategory =
|
||||
permissionService.getPermissionsByCategory(gestionnaireRole);
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"role", gestionnaireRole.getDisplayName(),
|
||||
"description", gestionnaireRole.getDescription(),
|
||||
"summary", summary,
|
||||
"specificities",
|
||||
Map.of(
|
||||
"clientManagement", "Gestion limitée aux clients assignés",
|
||||
"projectScope", "Chantiers et projets sous sa responsabilité uniquement",
|
||||
"budgetAccess", "Consultation et planification budgétaire",
|
||||
"materialReservation", "Réservation de matériel pour ses chantiers",
|
||||
"reportingLevel", "Rapports et statistiques de ses projets"),
|
||||
"categoriesDetails",
|
||||
byCategory.entrySet().stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
entry -> entry.getKey().getDisplayName(),
|
||||
entry ->
|
||||
entry.getValue().stream()
|
||||
.map(
|
||||
p ->
|
||||
Map.of(
|
||||
"code", p.getCode(),
|
||||
"description", p.getDescription()))
|
||||
.collect(Collectors.toList())))))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des permissions gestionnaire", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Valide les permissions requises pour une fonctionnalité */
|
||||
@POST
|
||||
@Path("/validate")
|
||||
public Response validatePermissions(ValidationRequest request) {
|
||||
try {
|
||||
logger.debug("POST /api/permissions/validate");
|
||||
|
||||
UserRole role = UserRole.valueOf(request.role.toUpperCase());
|
||||
Set<Permission> requiredPermissions =
|
||||
request.requiredPermissions.stream()
|
||||
.map(Permission::fromCode)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
boolean hasMinimum = permissionService.hasMinimumPermissions(role, requiredPermissions);
|
||||
|
||||
Map<String, Boolean> permissionChecks =
|
||||
request.requiredPermissions.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
code -> code, code -> permissionService.hasPermission(role, code)));
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"role",
|
||||
role.getDisplayName(),
|
||||
"hasMinimumPermissions",
|
||||
hasMinimum,
|
||||
"permissionChecks",
|
||||
permissionChecks,
|
||||
"missingPermissions",
|
||||
request.requiredPermissions.stream()
|
||||
.filter(code -> !permissionService.hasPermission(role, code))
|
||||
.collect(Collectors.toList())))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la validation des permissions", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la validation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class ValidationRequest {
|
||||
public String role;
|
||||
public List<String> requiredPermissions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.PhaseTemplateService;
|
||||
import dev.lions.btpxpress.domain.core.entity.PhaseChantier;
|
||||
import dev.lions.btpxpress.domain.core.entity.PhaseTemplate;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeChantierBTP;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des templates de phases BTP Expose les fonctionnalités de création,
|
||||
* consultation et administration des templates
|
||||
*/
|
||||
@Path("/api/v1/phase-templates")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Phase Templates", description = "Gestion des templates de phases BTP")
|
||||
public class PhaseTemplateResource {
|
||||
|
||||
@Inject PhaseTemplateService phaseTemplateService;
|
||||
|
||||
// ===================================
|
||||
// CONSULTATION DES TEMPLATES
|
||||
// ===================================
|
||||
|
||||
@GET
|
||||
@Path("/types-chantier")
|
||||
@Operation(summary = "Récupère tous les types de chantiers disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Liste des types de chantiers")
|
||||
public Response getTypesChantierDisponibles() {
|
||||
TypeChantierBTP[] types = phaseTemplateService.getTypesChantierDisponibles();
|
||||
return Response.ok(types).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/by-type/{typeChantier}")
|
||||
@Operation(summary = "Récupère tous les templates pour un type de chantier")
|
||||
@APIResponse(responseCode = "200", description = "Liste des templates pour le type de chantier")
|
||||
@APIResponse(responseCode = "400", description = "Type de chantier invalide")
|
||||
public Response getTemplatesByType(
|
||||
@Parameter(description = "Type de chantier BTP") @PathParam("typeChantier")
|
||||
String typeChantierStr) {
|
||||
|
||||
try {
|
||||
TypeChantierBTP typeChantier = TypeChantierBTP.valueOf(typeChantierStr.toUpperCase());
|
||||
List<PhaseTemplate> templates = phaseTemplateService.getTemplatesByType(typeChantier);
|
||||
return Response.ok(templates).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Type de chantier invalide: " + typeChantierStr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère un template par son ID avec ses sous-phases")
|
||||
@APIResponse(responseCode = "200", description = "Template trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Template non trouvé")
|
||||
public Response getTemplateById(
|
||||
@Parameter(description = "Identifiant du template") @PathParam("id") UUID id) {
|
||||
|
||||
return phaseTemplateService
|
||||
.getTemplateById(id)
|
||||
.map(template -> Response.ok(template).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupère tous les templates actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste de tous les templates actifs")
|
||||
public Response getAllTemplatesActifs() {
|
||||
List<PhaseTemplate> templates = phaseTemplateService.getAllTemplatesActifs();
|
||||
return Response.ok(templates).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/previsualisation/{typeChantier}")
|
||||
@Operation(summary = "Prévisualise les phases qui seraient générées pour un type de chantier")
|
||||
@APIResponse(responseCode = "200", description = "Prévisualisation des phases")
|
||||
@APIResponse(responseCode = "400", description = "Type de chantier invalide")
|
||||
public Response previsualiserPhases(
|
||||
@Parameter(description = "Type de chantier BTP") @PathParam("typeChantier")
|
||||
String typeChantierStr) {
|
||||
|
||||
try {
|
||||
TypeChantierBTP typeChantier = TypeChantierBTP.valueOf(typeChantierStr.toUpperCase());
|
||||
List<PhaseTemplate> templates = phaseTemplateService.previsualiserPhases(typeChantier);
|
||||
return Response.ok(templates).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Type de chantier invalide: " + typeChantierStr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/duree-estimee/{typeChantier}")
|
||||
@Operation(summary = "Calcule la durée totale estimée pour un type de chantier")
|
||||
@APIResponse(responseCode = "200", description = "Durée totale en jours")
|
||||
@APIResponse(responseCode = "400", description = "Type de chantier invalide")
|
||||
public Response calculerDureeTotaleEstimee(
|
||||
@Parameter(description = "Type de chantier BTP") @PathParam("typeChantier")
|
||||
String typeChantierStr) {
|
||||
|
||||
try {
|
||||
TypeChantierBTP typeChantier = TypeChantierBTP.valueOf(typeChantierStr.toUpperCase());
|
||||
Integer dureeTotal = phaseTemplateService.calculerDureeTotaleEstimee(typeChantier);
|
||||
return Response.ok(dureeTotal).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Type de chantier invalide: " + typeChantierStr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/complexite/{typeChantier}")
|
||||
@Operation(summary = "Analyse la complexité d'un type de chantier")
|
||||
@APIResponse(responseCode = "200", description = "Analyse de complexité")
|
||||
@APIResponse(responseCode = "400", description = "Type de chantier invalide")
|
||||
public Response analyserComplexite(
|
||||
@Parameter(description = "Type de chantier BTP") @PathParam("typeChantier")
|
||||
String typeChantierStr) {
|
||||
|
||||
try {
|
||||
TypeChantierBTP typeChantier = TypeChantierBTP.valueOf(typeChantierStr.toUpperCase());
|
||||
PhaseTemplateService.ComplexiteChantier complexite =
|
||||
phaseTemplateService.analyserComplexite(typeChantier);
|
||||
return Response.ok(complexite).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Type de chantier invalide: " + typeChantierStr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// GÉNÉRATION AUTOMATIQUE DE PHASES
|
||||
// ===================================
|
||||
|
||||
@POST
|
||||
@Path("/generer-phases")
|
||||
@Operation(summary = "Génère automatiquement les phases pour un chantier")
|
||||
@APIResponse(responseCode = "201", description = "Phases générées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "404", description = "Chantier non trouvé")
|
||||
public Response genererPhasesAutomatiquement(GenerationPhasesRequest request) {
|
||||
|
||||
if (request.chantierId == null || request.dateDebutChantier == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("L'ID du chantier et la date de début sont obligatoires")
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
List<PhaseChantier> phasesCreees =
|
||||
phaseTemplateService.genererPhasesAutomatiquement(
|
||||
request.chantierId,
|
||||
request.dateDebutChantier,
|
||||
request.inclureSousPhases != null ? request.inclureSousPhases : true);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(phasesCreees).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// ADMINISTRATION DES TEMPLATES
|
||||
// ===================================
|
||||
|
||||
@POST
|
||||
@Path("/initialize")
|
||||
@Operation(summary = "Initialise les templates de phases par défaut")
|
||||
@APIResponse(responseCode = "200", description = "Templates initialisés avec succès")
|
||||
@APIResponse(responseCode = "409", description = "Templates déjà existants")
|
||||
public Response initializeTemplates() {
|
||||
try {
|
||||
// Vérifier si des templates existent déjà
|
||||
List<PhaseTemplate> existingTemplates = phaseTemplateService.getAllTemplatesActifs();
|
||||
if (!existingTemplates.isEmpty()) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("message", "Des templates existent déjà", "count", existingTemplates.size()))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Initialisation des templates de phases par défaut
|
||||
List<PhaseTemplate> defaultTemplates = createDefaultPhaseTemplates();
|
||||
|
||||
for (PhaseTemplate template : defaultTemplates) {
|
||||
phaseTemplateService.creerTemplate(template);
|
||||
}
|
||||
|
||||
return Response.ok()
|
||||
.entity(Map.of(
|
||||
"message", "Templates initialisés avec succès",
|
||||
"count", defaultTemplates.size()
|
||||
))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'initialisation : " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Crée un nouveau template de phase")
|
||||
@APIResponse(responseCode = "201", description = "Template créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - template existant pour cet ordre")
|
||||
public Response creerTemplate(@Valid PhaseTemplate template) {
|
||||
try {
|
||||
PhaseTemplate nouveauTemplate = phaseTemplateService.creerTemplate(template);
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauTemplate).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour un template de phase")
|
||||
@APIResponse(responseCode = "200", description = "Template mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Template non trouvé")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - ordre d'exécution déjà utilisé")
|
||||
public Response updateTemplate(
|
||||
@Parameter(description = "Identifiant du template") @PathParam("id") UUID id,
|
||||
@Valid PhaseTemplate templateData) {
|
||||
|
||||
try {
|
||||
PhaseTemplate templateMisAJour = phaseTemplateService.updateTemplate(id, templateData);
|
||||
return Response.ok(templateMisAJour).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().contains("non trouvé")) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime un template (désactivation)")
|
||||
@APIResponse(responseCode = "204", description = "Template supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Template non trouvé")
|
||||
public Response supprimerTemplate(
|
||||
@Parameter(description = "Identifiant du template") @PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
phaseTemplateService.supprimerTemplate(id);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// CLASSES INTERNES - REQUESTS/RESPONSES
|
||||
// ===================================
|
||||
|
||||
/** Classe pour la requête de génération automatique de phases */
|
||||
public static class GenerationPhasesRequest {
|
||||
@NotNull(message = "L'ID du chantier est obligatoire")
|
||||
public UUID chantierId;
|
||||
|
||||
@NotNull(message = "La date de début du chantier est obligatoire")
|
||||
public LocalDate dateDebutChantier;
|
||||
|
||||
public Boolean inclureSousPhases = true;
|
||||
|
||||
// Constructeurs
|
||||
public GenerationPhasesRequest() {}
|
||||
|
||||
public GenerationPhasesRequest(
|
||||
UUID chantierId, LocalDate dateDebutChantier, Boolean inclureSousPhases) {
|
||||
this.chantierId = chantierId;
|
||||
this.dateDebutChantier = dateDebutChantier;
|
||||
this.inclureSousPhases = inclureSousPhases;
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe pour la réponse de génération de phases */
|
||||
public static class GenerationPhasesResponse {
|
||||
public List<PhaseChantier> phasesCreees;
|
||||
public int nombrePhasesGenerees;
|
||||
public int nombreSousPhasesGenerees;
|
||||
public String message;
|
||||
|
||||
public GenerationPhasesResponse(List<PhaseChantier> phasesCreees) {
|
||||
this.phasesCreees = phasesCreees;
|
||||
this.nombrePhasesGenerees = phasesCreees.size();
|
||||
this.nombreSousPhasesGenerees = calculerNombreSousPhases(phasesCreees);
|
||||
this.message =
|
||||
String.format(
|
||||
"Génération réussie: %d phases principales et %d sous-phases créées",
|
||||
nombrePhasesGenerees, nombreSousPhasesGenerees);
|
||||
}
|
||||
|
||||
private int calculerNombreSousPhases(List<PhaseChantier> phases) {
|
||||
return phases.stream()
|
||||
.mapToInt(phase -> phase.getSousPhases() != null ? phase.getSousPhases().size() : 0)
|
||||
.sum();
|
||||
}
|
||||
}
|
||||
|
||||
private List<PhaseTemplate> createDefaultPhaseTemplates() {
|
||||
List<PhaseTemplate> templates = new ArrayList<>();
|
||||
|
||||
// Template pour construction neuve
|
||||
PhaseTemplate constructionNeuve = new PhaseTemplate();
|
||||
constructionNeuve.setNom("Construction neuve - Standard");
|
||||
constructionNeuve.setDescription("Template pour construction de maison individuelle");
|
||||
constructionNeuve.setActif(true);
|
||||
templates.add(constructionNeuve);
|
||||
|
||||
// Template pour rénovation
|
||||
PhaseTemplate renovation = new PhaseTemplate();
|
||||
renovation.setNom("Rénovation - Standard");
|
||||
renovation.setDescription("Template pour rénovation complète");
|
||||
renovation.setActif(true);
|
||||
templates.add(renovation);
|
||||
|
||||
return templates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.PlanningMaterielService;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des plannings matériel EXPOSITION: Endpoints pour planification et
|
||||
* visualisation graphique
|
||||
*/
|
||||
@Path("/api/v1/plannings-materiel")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class PlanningMaterielResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PlanningMaterielResource.class);
|
||||
|
||||
@Inject PlanningMaterielService planningService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public Response findAll(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/ - page: {}, size: {}", page, size);
|
||||
|
||||
List<PlanningMateriel> plannings;
|
||||
if (page > 0 || size < 1000) {
|
||||
plannings = planningService.findAll(page, size);
|
||||
} else {
|
||||
plannings = planningService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(plannings).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response findById(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/{}", id);
|
||||
|
||||
PlanningMateriel planning = planningService.findByIdRequired(id);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/materiel/{materielId}")
|
||||
public Response findByMateriel(@PathParam("materielId") UUID materielId) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/materiel/{}", materielId);
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findByMateriel(materielId);
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings pour matériel: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/periode")
|
||||
public Response findByPeriode(
|
||||
@QueryParam("dateDebut") LocalDate dateDebut, @QueryParam("dateFin") LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug(
|
||||
"GET /api/plannings-materiel/periode?dateDebut={}&dateFin={}", dateDebut, dateFin);
|
||||
|
||||
if (dateDebut == null || dateFin == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Les paramètres dateDebut et dateFin sont obligatoires")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findByPeriode(dateDebut, dateFin);
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings par période", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
public Response findByStatut(@PathParam("statut") String statutStr) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/statut/{}", statutStr);
|
||||
|
||||
StatutPlanning statut = StatutPlanning.fromString(statutStr);
|
||||
List<PlanningMateriel> plannings = planningService.findByStatut(statut);
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings par statut: " + statutStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/type/{type}")
|
||||
public Response findByType(@PathParam("type") String typeStr) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/type/{}", typeStr);
|
||||
|
||||
TypePlanning type = TypePlanning.fromString(typeStr);
|
||||
List<PlanningMateriel> plannings = planningService.findByType(type);
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings par type: " + typeStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
public Response search(@QueryParam("terme") String terme) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/search?terme={}", terme);
|
||||
|
||||
List<PlanningMateriel> resultats = planningService.search(terme);
|
||||
return Response.ok(resultats).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche avec terme: " + terme, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS MÉTIER SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/avec-conflits")
|
||||
public Response findAvecConflits() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/avec-conflits");
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findAvecConflits();
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings avec conflits", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/necessitant-attention")
|
||||
public Response findNecessitantAttention() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/necessitant-attention");
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findNecessitantAttention();
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings nécessitant attention", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard-validation")
|
||||
public Response findEnRetardValidation() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/en-retard-validation");
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findEnRetardValidation();
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/prioritaires")
|
||||
public Response findPrioritaires() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/prioritaires");
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findPrioritaires();
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings prioritaires", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-cours")
|
||||
public Response findEnCours() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/en-cours");
|
||||
|
||||
List<PlanningMateriel> plannings = planningService.findEnCours();
|
||||
return Response.ok(plannings).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des plannings en cours", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des plannings: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION ET MODIFICATION ===
|
||||
|
||||
@POST
|
||||
@Path("/")
|
||||
public Response createPlanning(@Valid CreatePlanningRequest request) {
|
||||
try {
|
||||
logger.info("POST /api/plannings-materiel/ - création planning");
|
||||
|
||||
PlanningMateriel planning =
|
||||
planningService.createPlanning(
|
||||
request.materielId,
|
||||
request.nomPlanning,
|
||||
request.descriptionPlanning,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.typePlanning,
|
||||
request.planificateur);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(planning).build();
|
||||
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du planning", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public Response updatePlanning(@PathParam("id") UUID id, @Valid UpdatePlanningRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}", id);
|
||||
|
||||
PlanningMateriel planning =
|
||||
planningService.updatePlanning(
|
||||
id,
|
||||
request.nomPlanning,
|
||||
request.descriptionPlanning,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.modifiePar);
|
||||
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION DU WORKFLOW ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/valider")
|
||||
public Response validerPlanning(@PathParam("id") UUID id, @Valid ValiderPlanningRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/valider", id);
|
||||
|
||||
PlanningMateriel planning =
|
||||
planningService.validerPlanning(id, request.valideur, request.commentaires);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la validation du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la validation du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/mettre-en-revision")
|
||||
public Response mettreEnRevision(
|
||||
@PathParam("id") UUID id, @Valid RevisionPlanningRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/mettre-en-revision", id);
|
||||
|
||||
PlanningMateriel planning = planningService.mettreEnRevision(id, request.motif);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise en révision du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise en révision du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/archiver")
|
||||
public Response archiverPlanning(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/archiver", id);
|
||||
|
||||
PlanningMateriel planning = planningService.archiverPlanning(id);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'archivage du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'archivage du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/suspendre")
|
||||
public Response suspendrePlanning(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/suspendre", id);
|
||||
|
||||
PlanningMateriel planning = planningService.suspendrePlanning(id);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suspension du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suspension du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/reactiver")
|
||||
public Response reactiverPlanning(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/reactiver", id);
|
||||
|
||||
PlanningMateriel planning = planningService.reactiverPlanning(id);
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la réactivation du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la réactivation du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE VÉRIFICATION ET ANALYSE ===
|
||||
|
||||
@GET
|
||||
@Path("/check-conflits")
|
||||
public Response checkConflits(
|
||||
@QueryParam("materielId") @NotNull UUID materielId,
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin,
|
||||
@QueryParam("excludeId") UUID excludeId) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/check-conflits");
|
||||
|
||||
List<PlanningMateriel> conflitsList =
|
||||
planningService.checkConflits(materielId, dateDebut, dateFin, excludeId);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public boolean disponible = conflitsList.isEmpty();
|
||||
public int nombreConflits = conflitsList.size();
|
||||
public List<PlanningMateriel> conflits = conflitsList;
|
||||
})
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la vérification des conflits", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la vérification des conflits: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/disponibilite/{materielId}")
|
||||
public Response analyserDisponibilite(
|
||||
@PathParam("materielId") UUID materielId,
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/disponibilite/{}", materielId);
|
||||
|
||||
Map<String, Object> disponibilite =
|
||||
planningService.analyserDisponibilite(materielId, dateDebut, dateFin);
|
||||
return Response.ok(disponibilite).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse de disponibilité: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse de disponibilité: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'OPTIMISATION ===
|
||||
|
||||
@POST
|
||||
@Path("/optimiser")
|
||||
public Response optimiserPlannings() {
|
||||
try {
|
||||
logger.info("POST /api/plannings-materiel/optimiser");
|
||||
|
||||
List<PlanningMateriel> optimises = planningService.optimiserPlannings();
|
||||
return Response.ok(Map.of("nombreOptimises", optimises.size(), "plannings", optimises))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'optimisation des plannings", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'optimisation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/optimiser")
|
||||
public Response optimiserPlanning(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.info("PUT /api/plannings-materiel/{}/optimiser", id);
|
||||
|
||||
PlanningMateriel planning = planningService.findByIdRequired(id);
|
||||
planningService.optimiserPlanning(planning);
|
||||
|
||||
return Response.ok(planning).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'optimisation du planning: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'optimisation du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/statistiques");
|
||||
|
||||
Map<String, Object> statistiques = planningService.getStatistiques();
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tableau-bord")
|
||||
public Response getTableauBord() {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/tableau-bord");
|
||||
|
||||
Map<String, Object> tableauBord = planningService.getTableauBordPlannings();
|
||||
return Response.ok(tableauBord).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération du tableau de bord", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération du tableau de bord: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/taux-utilisation")
|
||||
public Response analyserTauxUtilisation(
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug("GET /api/plannings-materiel/taux-utilisation");
|
||||
|
||||
List<Object> analyse = planningService.analyserTauxUtilisation(dateDebut, dateFin);
|
||||
return Response.ok(analyse).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse des taux d'utilisation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === MAINTENANCE AUTOMATIQUE ===
|
||||
|
||||
@POST
|
||||
@Path("/verifier-conflits")
|
||||
public Response verifierTousConflits() {
|
||||
try {
|
||||
logger.info("POST /api/plannings-materiel/verifier-conflits");
|
||||
|
||||
planningService.verifierTousConflits();
|
||||
return Response.ok(Map.of("message", "Vérification des conflits terminée")).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la vérification des conflits", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la vérification des conflits: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreatePlanningRequest {
|
||||
@NotNull public UUID materielId;
|
||||
|
||||
@NotNull public String nomPlanning;
|
||||
|
||||
public String descriptionPlanning;
|
||||
|
||||
@NotNull public LocalDate dateDebut;
|
||||
|
||||
@NotNull public LocalDate dateFin;
|
||||
|
||||
@NotNull public TypePlanning typePlanning;
|
||||
|
||||
public String planificateur;
|
||||
}
|
||||
|
||||
public static class UpdatePlanningRequest {
|
||||
public String nomPlanning;
|
||||
public String descriptionPlanning;
|
||||
public LocalDate dateDebut;
|
||||
public LocalDate dateFin;
|
||||
public String modifiePar;
|
||||
}
|
||||
|
||||
public static class ValiderPlanningRequest {
|
||||
@NotNull public String valideur;
|
||||
|
||||
public String commentaires;
|
||||
}
|
||||
|
||||
public static class RevisionPlanningRequest {
|
||||
@NotNull public String motif;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.ReservationMaterielService;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des réservations matériel EXPOSITION: Endpoints pour l'affectation et
|
||||
* planification matériel/chantier
|
||||
*/
|
||||
@Path("/api/v1/reservations-materiel")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ReservationMaterielResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReservationMaterielResource.class);
|
||||
|
||||
@Inject ReservationMaterielService reservationService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public Response findAll(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/ - page: {}, size: {}", page, size);
|
||||
|
||||
List<ReservationMateriel> reservations;
|
||||
if (page > 0 || size < 1000) {
|
||||
reservations = reservationService.findAll(page, size);
|
||||
} else {
|
||||
reservations = reservationService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(reservations).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response findById(@PathParam("id") UUID id) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/{}", id);
|
||||
|
||||
ReservationMateriel reservation = reservationService.findByIdRequired(id);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de la réservation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reference/{reference}")
|
||||
public Response findByReference(@PathParam("reference") String reference) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/reference/{}", reference);
|
||||
|
||||
return reservationService
|
||||
.findByReference(reference)
|
||||
.map(reservation -> Response.ok(reservation).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Réservation non trouvée avec la référence: " + reference)
|
||||
.build());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération de la réservation par référence: " + reference, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/materiel/{materielId}")
|
||||
public Response findByMateriel(@PathParam("materielId") UUID materielId) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/materiel/{}", materielId);
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findByMateriel(materielId);
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des réservations pour matériel: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
public Response findByChantier(@PathParam("chantierId") UUID chantierId) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/chantier/{}", chantierId);
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findByChantier(chantierId);
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Erreur lors de la récupération des réservations pour chantier: " + chantierId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
public Response findByStatut(@PathParam("statut") String statutStr) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/statut/{}", statutStr);
|
||||
|
||||
StatutReservationMateriel statut = StatutReservationMateriel.fromString(statutStr);
|
||||
List<ReservationMateriel> reservations = reservationService.findByStatut(statut);
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations par statut: " + statutStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/periode")
|
||||
public Response findByPeriode(
|
||||
@QueryParam("dateDebut") LocalDate dateDebut, @QueryParam("dateFin") LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug(
|
||||
"GET /api/reservations-materiel/periode?dateDebut={}&dateFin={}", dateDebut, dateFin);
|
||||
|
||||
if (dateDebut == null || dateFin == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Les paramètres dateDebut et dateFin sont obligatoires")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findByPeriode(dateDebut, dateFin);
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations par période", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS MÉTIER SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/en-attente-validation")
|
||||
public Response findEnAttenteValidation() {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/en-attente-validation");
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findEnAttenteValidation();
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations en attente", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
public Response findEnRetard() {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/en-retard");
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findEnRetard();
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/prioritaires")
|
||||
public Response findPrioritaires() {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/prioritaires");
|
||||
|
||||
List<ReservationMateriel> reservations = reservationService.findPrioritaires();
|
||||
return Response.ok(reservations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des réservations prioritaires", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des réservations: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
public Response search(@QueryParam("terme") String terme) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/search?terme={}", terme);
|
||||
|
||||
List<ReservationMateriel> resultats = reservationService.search(terme);
|
||||
return Response.ok(resultats).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche avec terme: " + terme, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la recherche: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION ET MODIFICATION ===
|
||||
|
||||
@POST
|
||||
@Path("/")
|
||||
public Response createReservation(@Valid CreateReservationRequest request) {
|
||||
try {
|
||||
logger.info("POST /api/reservations-materiel/ - création réservation");
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.createReservation(
|
||||
request.materielId,
|
||||
request.chantierId,
|
||||
request.phaseId,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.quantite,
|
||||
request.unite,
|
||||
request.demandeur,
|
||||
request.lieuLivraison);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(reservation).build();
|
||||
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création de la réservation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public Response updateReservation(
|
||||
@PathParam("id") UUID id, @Valid UpdateReservationRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}", id);
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.updateReservation(
|
||||
id,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.quantite,
|
||||
request.lieuLivraison,
|
||||
request.instructionsLivraison,
|
||||
request.priorite);
|
||||
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour de la réservation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION DU WORKFLOW ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/valider")
|
||||
public Response validerReservation(
|
||||
@PathParam("id") UUID id, @Valid ValiderReservationRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}/valider", id);
|
||||
|
||||
ReservationMateriel reservation = reservationService.validerReservation(id, request.valideur);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la validation de la réservation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la validation de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/refuser")
|
||||
public Response refuserReservation(
|
||||
@PathParam("id") UUID id, @Valid RefuserReservationRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}/refuser", id);
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.refuserReservation(id, request.valideur, request.motifRefus);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du refus de la réservation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du refus de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/livrer")
|
||||
public Response livrerMateriel(@PathParam("id") UUID id, @Valid LivrerMaterielRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}/livrer", id);
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.livrerMateriel(
|
||||
id, request.dateLivraison, request.observations, request.etatMateriel);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la livraison du matériel: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la livraison du matériel: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/retourner")
|
||||
public Response retournerMateriel(
|
||||
@PathParam("id") UUID id, @Valid RetournerMaterielRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}/retourner", id);
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.retournerMateriel(
|
||||
id, request.dateRetour, request.observations, request.etatMateriel, request.prixReel);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du retour du matériel: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du retour du matériel: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/annuler")
|
||||
public Response annulerReservation(
|
||||
@PathParam("id") UUID id, @Valid AnnulerReservationRequest request) {
|
||||
try {
|
||||
logger.info("PUT /api/reservations-materiel/{}/annuler", id);
|
||||
|
||||
ReservationMateriel reservation =
|
||||
reservationService.annulerReservation(id, request.motifAnnulation);
|
||||
return Response.ok(reservation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'annulation de la réservation: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'annulation de la réservation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE VÉRIFICATION ===
|
||||
|
||||
@GET
|
||||
@Path("/check-conflits")
|
||||
public Response checkConflits(
|
||||
@QueryParam("materielId") @NotNull UUID materielId,
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin,
|
||||
@QueryParam("excludeId") UUID excludeId) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/check-conflits");
|
||||
|
||||
List<ReservationMateriel> conflitsList =
|
||||
reservationService.checkConflits(materielId, dateDebut, dateFin, excludeId);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public boolean disponible = conflitsList.isEmpty();
|
||||
public int nombreConflits = conflitsList.size();
|
||||
public List<ReservationMateriel> conflits = conflitsList;
|
||||
})
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la vérification des conflits", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la vérification des conflits: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/disponibilite/{materielId}")
|
||||
public Response getDisponibiliteMateriel(
|
||||
@PathParam("materielId") UUID materielId,
|
||||
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
|
||||
@QueryParam("dateFin") @NotNull LocalDate dateFin) {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/disponibilite/{}", materielId);
|
||||
|
||||
Map<String, Object> disponibilite =
|
||||
reservationService.getDisponibiliteMateriel(materielId, dateDebut, dateFin);
|
||||
return Response.ok(disponibilite).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'analyse de disponibilité: " + materielId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'analyse de disponibilité: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/statistiques");
|
||||
|
||||
Object statistiques = reservationService.getStatistiques();
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tableau-bord")
|
||||
public Response getTableauBord() {
|
||||
try {
|
||||
logger.debug("GET /api/reservations-materiel/tableau-bord");
|
||||
|
||||
Object tableauBord = reservationService.getTableauBordReservations();
|
||||
return Response.ok(tableauBord).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération du tableau de bord", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération du tableau de bord: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreateReservationRequest {
|
||||
@NotNull public UUID materielId;
|
||||
|
||||
@NotNull public UUID chantierId;
|
||||
|
||||
public UUID phaseId;
|
||||
|
||||
@NotNull public LocalDate dateDebut;
|
||||
|
||||
@NotNull public LocalDate dateFin;
|
||||
|
||||
@NotNull public BigDecimal quantite;
|
||||
|
||||
public String unite;
|
||||
public String demandeur;
|
||||
public String lieuLivraison;
|
||||
}
|
||||
|
||||
public static class UpdateReservationRequest {
|
||||
public LocalDate dateDebut;
|
||||
public LocalDate dateFin;
|
||||
public BigDecimal quantite;
|
||||
public String lieuLivraison;
|
||||
public String instructionsLivraison;
|
||||
public PrioriteReservation priorite;
|
||||
}
|
||||
|
||||
public static class ValiderReservationRequest {
|
||||
@NotNull public String valideur;
|
||||
}
|
||||
|
||||
public static class RefuserReservationRequest {
|
||||
@NotNull public String valideur;
|
||||
|
||||
@NotNull public String motifRefus;
|
||||
}
|
||||
|
||||
public static class LivrerMaterielRequest {
|
||||
public LocalDate dateLivraison;
|
||||
public String observations;
|
||||
public String etatMateriel;
|
||||
}
|
||||
|
||||
public static class RetournerMaterielRequest {
|
||||
public LocalDate dateRetour;
|
||||
public String observations;
|
||||
public String etatMateriel;
|
||||
public BigDecimal prixReel;
|
||||
}
|
||||
|
||||
public static class AnnulerReservationRequest {
|
||||
@NotNull public String motifAnnulation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.domain.core.entity.PhaseTemplate;
|
||||
import dev.lions.btpxpress.domain.core.entity.SousPhaseTemplate;
|
||||
import dev.lions.btpxpress.domain.infrastructure.repository.PhaseTemplateRepository;
|
||||
import dev.lions.btpxpress.domain.infrastructure.repository.SousPhaseTemplateRepository;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des templates de sous-phases BTP Fournit les opérations CRUD pour les
|
||||
* sous-phases de templates
|
||||
*/
|
||||
@Path("/api/v1/sous-phase-templates")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Sous-Phase Templates", description = "Gestion des templates de sous-phases BTP")
|
||||
public class SousPhaseTemplateResource {
|
||||
|
||||
@Inject SousPhaseTemplateRepository sousPhaseTemplateRepository;
|
||||
|
||||
@Inject PhaseTemplateRepository phaseTemplateRepository;
|
||||
|
||||
// ===================================
|
||||
// CONSULTATION DES SOUS-PHASE TEMPLATES
|
||||
// ===================================
|
||||
|
||||
@GET
|
||||
@Path("/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Récupère toutes les sous-phases d'une phase template")
|
||||
@APIResponse(responseCode = "200", description = "Liste des sous-phases pour la phase template")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response getSousPhasesByPhaseTemplate(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
// Vérifier que la phase template existe
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SousPhaseTemplate> sousPhases =
|
||||
sousPhaseTemplateRepository.findByPhaseTemplate(phaseTemplate);
|
||||
return Response.ok(sousPhases).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère une sous-phase template par son ID")
|
||||
@APIResponse(responseCode = "200", description = "Sous-phase template trouvée")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getSousPhaseTemplateById(
|
||||
@Parameter(description = "Identifiant de la sous-phase template") @PathParam("id") UUID id) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(id);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
return Response.ok(sousPhaseTemplate).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/critiques/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Récupère les sous-phases critiques d'une phase template")
|
||||
@APIResponse(responseCode = "200", description = "Liste des sous-phases critiques")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response getSousPhasesCritiques(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SousPhaseTemplate> sousPhasesCritiques =
|
||||
sousPhaseTemplateRepository.findCritiquesByPhase(phaseTemplate);
|
||||
return Response.ok(sousPhasesCritiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/with-qualified-workers/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Récupère les sous-phases nécessitant du personnel qualifié")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des sous-phases avec personnel qualifié requis")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response getSousPhaseAvecPersonnelQualifie(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SousPhaseTemplate> sousPhases =
|
||||
sousPhaseTemplateRepository.findRequiringQualifiedWorkers(phaseTemplate);
|
||||
return Response.ok(sousPhases).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/with-materials/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Récupère les sous-phases avec matériels spécifiques")
|
||||
@APIResponse(responseCode = "200", description = "Liste des sous-phases avec matériels")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response getSousPhaseAvecMateriels(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SousPhaseTemplate> sousPhases =
|
||||
sousPhaseTemplateRepository.findWithSpecificMaterials(phaseTemplate);
|
||||
return Response.ok(sousPhases).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/duree-totale/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Calcule la durée totale des sous-phases d'une phase template")
|
||||
@APIResponse(responseCode = "200", description = "Durée totale en jours")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response calculerDureeTotaleSousPhases(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
Integer dureeTotal = sousPhaseTemplateRepository.calculateDureeTotale(phaseTemplate);
|
||||
return Response.ok(dureeTotal).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count/by-phase/{phaseTemplateId}")
|
||||
@Operation(summary = "Compte le nombre de sous-phases pour une phase template")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de sous-phases actives")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response compterSousPhases(
|
||||
@Parameter(description = "ID de la phase template parent") @PathParam("phaseTemplateId")
|
||||
UUID phaseTemplateId) {
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
long count = sousPhaseTemplateRepository.countByPhaseTemplate(phaseTemplate);
|
||||
return Response.ok(count).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Recherche de sous-phases par nom")
|
||||
@APIResponse(responseCode = "200", description = "Liste des sous-phases correspondantes")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de recherche invalides")
|
||||
@APIResponse(responseCode = "404", description = "Phase template non trouvée")
|
||||
public Response rechercherSousPhases(
|
||||
@Parameter(description = "ID de la phase template parent") @QueryParam("phaseTemplateId")
|
||||
UUID phaseTemplateId,
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("searchTerm") String searchTerm) {
|
||||
|
||||
if (phaseTemplateId == null || searchTerm == null || searchTerm.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("L'ID de la phase template et le terme de recherche sont obligatoires")
|
||||
.build();
|
||||
}
|
||||
|
||||
PhaseTemplate phaseTemplate = phaseTemplateRepository.findById(phaseTemplateId);
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template non trouvée avec l'ID: " + phaseTemplateId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<SousPhaseTemplate> resultats =
|
||||
sousPhaseTemplateRepository.searchByNom(phaseTemplate, searchTerm.trim());
|
||||
return Response.ok(resultats).build();
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// ADMINISTRATION DES SOUS-PHASE TEMPLATES
|
||||
// ===================================
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Crée une nouvelle sous-phase template")
|
||||
@APIResponse(responseCode = "201", description = "Sous-phase template créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Phase template parent non trouvée")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - sous-phase existante pour cet ordre")
|
||||
public Response creerSousPhaseTemplate(@Valid SousPhaseTemplate sousPhaseTemplate) {
|
||||
|
||||
// Vérifier que la phase template parent existe
|
||||
if (sousPhaseTemplate.getPhaseParent() == null
|
||||
|| sousPhaseTemplate.getPhaseParent().getId() == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("La phase template parent est obligatoire")
|
||||
.build();
|
||||
}
|
||||
|
||||
PhaseTemplate phaseTemplate =
|
||||
phaseTemplateRepository.findById(sousPhaseTemplate.getPhaseParent().getId());
|
||||
if (phaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase template parent non trouvée")
|
||||
.build();
|
||||
}
|
||||
|
||||
// Vérifier l'unicité de l'ordre d'exécution
|
||||
if (sousPhaseTemplate.getOrdreExecution() != null
|
||||
&& sousPhaseTemplateRepository.existsByPhaseAndOrdre(
|
||||
phaseTemplate, sousPhaseTemplate.getOrdreExecution(), null)) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(
|
||||
"Une sous-phase existe déjà pour cette phase à l'ordre "
|
||||
+ sousPhaseTemplate.getOrdreExecution())
|
||||
.build();
|
||||
}
|
||||
|
||||
// Si aucun ordre spécifié, utiliser le prochain disponible
|
||||
if (sousPhaseTemplate.getOrdreExecution() == null) {
|
||||
sousPhaseTemplate.setOrdreExecution(
|
||||
sousPhaseTemplateRepository.getNextOrdreExecution(phaseTemplate));
|
||||
}
|
||||
|
||||
// Assigner la phase template parent
|
||||
sousPhaseTemplate.setPhaseParent(phaseTemplate);
|
||||
|
||||
sousPhaseTemplateRepository.persist(sousPhaseTemplate);
|
||||
return Response.status(Response.Status.CREATED).entity(sousPhaseTemplate).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour une sous-phase template")
|
||||
@APIResponse(responseCode = "200", description = "Sous-phase template mise à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - ordre d'exécution déjà utilisé")
|
||||
public Response updateSousPhaseTemplate(
|
||||
@Parameter(description = "Identifiant de la sous-phase template") @PathParam("id") UUID id,
|
||||
@Valid SousPhaseTemplate sousPhaseData) {
|
||||
|
||||
SousPhaseTemplate existingSousPhase = sousPhaseTemplateRepository.findById(id);
|
||||
if (existingSousPhase == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
|
||||
// Vérifier l'unicité de l'ordre d'exécution si modifié
|
||||
if (sousPhaseData.getOrdreExecution() != null
|
||||
&& !sousPhaseData.getOrdreExecution().equals(existingSousPhase.getOrdreExecution())
|
||||
&& sousPhaseTemplateRepository.existsByPhaseAndOrdre(
|
||||
existingSousPhase.getPhaseParent(), sousPhaseData.getOrdreExecution(), id)) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(
|
||||
"Une autre sous-phase existe déjà pour cette phase à l'ordre "
|
||||
+ sousPhaseData.getOrdreExecution())
|
||||
.build();
|
||||
}
|
||||
|
||||
// Mettre à jour les champs
|
||||
existingSousPhase.setNom(sousPhaseData.getNom());
|
||||
existingSousPhase.setDescription(sousPhaseData.getDescription());
|
||||
existingSousPhase.setOrdreExecution(sousPhaseData.getOrdreExecution());
|
||||
existingSousPhase.setDureePrevueJours(sousPhaseData.getDureePrevueJours());
|
||||
existingSousPhase.setDureeEstimeeHeures(sousPhaseData.getDureeEstimeeHeures());
|
||||
existingSousPhase.setCritique(sousPhaseData.getCritique());
|
||||
existingSousPhase.setPriorite(sousPhaseData.getPriorite());
|
||||
existingSousPhase.setMaterielsTypes(sousPhaseData.getMaterielsTypes());
|
||||
existingSousPhase.setCompetencesRequises(sousPhaseData.getCompetencesRequises());
|
||||
existingSousPhase.setOutilsNecessaires(sousPhaseData.getOutilsNecessaires());
|
||||
existingSousPhase.setInstructionsExecution(sousPhaseData.getInstructionsExecution());
|
||||
existingSousPhase.setPointsControle(sousPhaseData.getPointsControle());
|
||||
existingSousPhase.setCriteresValidation(sousPhaseData.getCriteresValidation());
|
||||
existingSousPhase.setPrecautionsSecurite(sousPhaseData.getPrecautionsSecurite());
|
||||
existingSousPhase.setConditionsExecution(sousPhaseData.getConditionsExecution());
|
||||
existingSousPhase.setTempsPreparationMinutes(sousPhaseData.getTempsPreparationMinutes());
|
||||
existingSousPhase.setTempsFinitionMinutes(sousPhaseData.getTempsFinitionMinutes());
|
||||
existingSousPhase.setNombreOperateursRequis(sousPhaseData.getNombreOperateursRequis());
|
||||
existingSousPhase.setNiveauQualification(sousPhaseData.getNiveauQualification());
|
||||
|
||||
return Response.ok(existingSousPhase).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime une sous-phase template (désactivation)")
|
||||
@APIResponse(responseCode = "204", description = "Sous-phase template supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response supprimerSousPhaseTemplate(
|
||||
@Parameter(description = "Identifiant de la sous-phase template") @PathParam("id") UUID id) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(id);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
|
||||
int updated = sousPhaseTemplateRepository.desactiver(id);
|
||||
if (updated > 0) {
|
||||
return Response.noContent().build();
|
||||
} else {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de la sous-phase template")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/reactiver")
|
||||
@Operation(summary = "Réactive une sous-phase template")
|
||||
@APIResponse(responseCode = "200", description = "Sous-phase template réactivée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response reactiverSousPhaseTemplate(
|
||||
@Parameter(description = "Identifiant de la sous-phase template") @PathParam("id") UUID id) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(id);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
|
||||
int updated = sousPhaseTemplateRepository.reactiver(id);
|
||||
if (updated > 0) {
|
||||
sousPhaseTemplate.setActif(true);
|
||||
return Response.ok(sousPhaseTemplate).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la réactivation de la sous-phase template")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.StockService;
|
||||
import dev.lions.btpxpress.domain.core.entity.CategorieStock;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutStock;
|
||||
import dev.lions.btpxpress.domain.core.entity.Stock;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des stocks et inventaires BTP
|
||||
* Architecture 2025 : API complète pour la gestion des stocks, entrées/sorties, réservations
|
||||
*/
|
||||
@Path("/api/v1/stocks")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Stocks", description = "Gestion des stocks et inventaires BTP")
|
||||
public class StockResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StockResource.class);
|
||||
|
||||
@Inject StockService stockService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les stocks", description = "Retourne la liste complète des articles en stock")
|
||||
@APIResponse(responseCode = "200", description = "Liste des stocks récupérée avec succès")
|
||||
public Response getAllStocks() {
|
||||
logger.debug("GET /stocks");
|
||||
try {
|
||||
List<Stock> stocks = stockService.findAll();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("stocks", stocks);
|
||||
response.put("total", stocks.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des stocks", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des stocks", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un stock par ID", description = "Retourne les détails d'un article en stock")
|
||||
@APIResponse(responseCode = "200", description = "Stock trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
public Response getStockById(@Parameter(description = "ID du stock") @PathParam("id") UUID id) {
|
||||
logger.debug("GET /stocks/{}", id);
|
||||
try {
|
||||
Stock stock = stockService.findById(id);
|
||||
return Response.ok(stock).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du stock: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération du stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reference/{reference}")
|
||||
@Operation(summary = "Récupérer un stock par référence", description = "Recherche un article en stock par sa référence")
|
||||
@APIResponse(responseCode = "200", description = "Stock trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
public Response getStockByReference(@Parameter(description = "Référence du stock") @PathParam("reference") String reference) {
|
||||
logger.debug("GET /stocks/reference/{}", reference);
|
||||
try {
|
||||
Stock stock = stockService.findByReference(reference);
|
||||
if (stock == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Stock non trouvé"))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(stock).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du stock par référence: {}", reference, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération du stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Rechercher des stocks", description = "Recherche textuelle dans les stocks")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
@APIResponse(responseCode = "400", description = "Terme de recherche requis")
|
||||
public Response searchStocks(@Parameter(description = "Terme de recherche") @QueryParam("term") String searchTerm) {
|
||||
logger.debug("GET /stocks/search - term: {}", searchTerm);
|
||||
try {
|
||||
if (searchTerm == null || searchTerm.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Terme de recherche requis"))
|
||||
.build();
|
||||
}
|
||||
List<Stock> stocks = stockService.searchStocks(searchTerm);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("stocks", stocks);
|
||||
response.put("total", stocks.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche de stocks: {}", searchTerm, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/categorie/{categorie}")
|
||||
@Operation(summary = "Récupérer les stocks par catégorie", description = "Filtre les stocks par catégorie")
|
||||
@APIResponse(responseCode = "200", description = "Liste des stocks filtrés")
|
||||
public Response getStocksByCategorie(@Parameter(description = "Catégorie de stock") @PathParam("categorie") String categorieStr) {
|
||||
logger.debug("GET /stocks/categorie/{}", categorieStr);
|
||||
try {
|
||||
CategorieStock categorie = CategorieStock.valueOf(categorieStr.toUpperCase());
|
||||
List<Stock> stocks = stockService.findByCategorie(categorie);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("stocks", stocks);
|
||||
response.put("total", stocks.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Catégorie invalide: {}", categorieStr, e);
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Catégorie invalide", "valeurs_acceptees", java.util.Arrays.toString(CategorieStock.values())))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des stocks par catégorie: {}", categorieStr, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des stocks"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(summary = "Récupérer les stocks par statut", description = "Filtre les stocks par statut")
|
||||
@APIResponse(responseCode = "200", description = "Liste des stocks filtrés")
|
||||
public Response getStocksByStatut(@Parameter(description = "Statut du stock") @PathParam("statut") StatutStock statut) {
|
||||
logger.debug("GET /stocks/statut/{}", statut);
|
||||
try {
|
||||
List<Stock> stocks = stockService.findByStatut(statut);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("stocks", stocks);
|
||||
response.put("total", stocks.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des stocks par statut: {}", statut, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des stocks"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/rupture")
|
||||
@Operation(summary = "Récupérer les stocks en rupture", description = "Liste les articles en rupture de stock")
|
||||
@APIResponse(responseCode = "200", description = "Liste des stocks en rupture")
|
||||
public Response getStocksEnRupture() {
|
||||
logger.debug("GET /stocks/rupture");
|
||||
try {
|
||||
List<Stock> stocks = stockService.findStocksEnRupture();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("stocks", stocks);
|
||||
response.put("total", stocks.size());
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des stocks en rupture", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des stocks"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupérer les statistiques des stocks", description = "Retourne des statistiques globales sur les stocks")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
logger.debug("GET /stocks/statistiques");
|
||||
try {
|
||||
Map<String, Object> stats = stockService.getStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/valeur-totale")
|
||||
@Operation(summary = "Calculer la valeur totale du stock", description = "Calcule la valeur totale de tous les stocks")
|
||||
@APIResponse(responseCode = "200", description = "Valeur totale calculée")
|
||||
public Response getValeurTotaleStock() {
|
||||
logger.debug("GET /stocks/valeur-totale");
|
||||
try {
|
||||
BigDecimal valeurTotale = stockService.calculateValeurTotaleStock();
|
||||
return Response.ok(Map.of("valeurTotale", valeurTotale)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du calcul de la valeur totale", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du calcul de la valeur totale"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@POST
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer un nouveau stock", description = "Crée un nouvel article en stock")
|
||||
@APIResponse(responseCode = "201", description = "Stock créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createStock(@Valid @NotNull Stock stock) {
|
||||
logger.info("POST /stocks - Création d'un stock: {}", stock.getReference());
|
||||
try {
|
||||
Stock nouveauStock = stockService.create(stock);
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauStock).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du stock", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la création du stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MISE À JOUR - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Mettre à jour un stock", description = "Met à jour les informations d'un article en stock")
|
||||
@APIResponse(responseCode = "200", description = "Stock mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateStock(
|
||||
@Parameter(description = "ID du stock") @PathParam("id") UUID id,
|
||||
@Valid @NotNull Stock stockData) {
|
||||
logger.info("PUT /stocks/{} - Mise à jour", id);
|
||||
try {
|
||||
Stock stock = stockService.update(id, stockData);
|
||||
return Response.ok(stock).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour du stock: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la mise à jour du stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/entree")
|
||||
@Authenticated
|
||||
@Operation(summary = "Enregistrer une entrée de stock", description = "Ajoute une quantité au stock")
|
||||
@APIResponse(responseCode = "200", description = "Entrée enregistrée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response entreeStock(
|
||||
@Parameter(description = "ID du stock") @PathParam("id") UUID id,
|
||||
Map<String, Object> payload) {
|
||||
logger.info("POST /stocks/{}/entree", id);
|
||||
try {
|
||||
BigDecimal quantite = new BigDecimal(payload.get("quantite").toString());
|
||||
String motif = payload.get("motif") != null ? payload.get("motif").toString() : null;
|
||||
String numeroDocument = payload.get("numeroDocument") != null ? payload.get("numeroDocument").toString() : null;
|
||||
|
||||
Stock stock = stockService.entreeStock(id, quantite, motif, numeroDocument);
|
||||
return Response.ok(stock).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Quantité ou données invalides"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'entrée de stock: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'entrée de stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/sortie")
|
||||
@Authenticated
|
||||
@Operation(summary = "Enregistrer une sortie de stock", description = "Retire une quantité du stock")
|
||||
@APIResponse(responseCode = "200", description = "Sortie enregistrée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Quantité insuffisante")
|
||||
public Response sortieStock(
|
||||
@Parameter(description = "ID du stock") @PathParam("id") UUID id,
|
||||
Map<String, Object> payload) {
|
||||
logger.info("POST /stocks/{}/sortie", id);
|
||||
try {
|
||||
BigDecimal quantite = new BigDecimal(payload.get("quantite").toString());
|
||||
String motif = payload.get("motif") != null ? payload.get("motif").toString() : null;
|
||||
String numeroDocument = payload.get("numeroDocument") != null ? payload.get("numeroDocument").toString() : null;
|
||||
|
||||
Stock stock = stockService.sortieStock(id, quantite, motif, numeroDocument);
|
||||
return Response.ok(stock).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Quantité insuffisante ou données invalides"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la sortie de stock: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la sortie de stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE SUPPRESSION - ARCHITECTURE 2025 ===
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Supprimer un stock", description = "Supprime un article du stock")
|
||||
@APIResponse(responseCode = "204", description = "Stock supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Stock non trouvé")
|
||||
public Response deleteStock(@Parameter(description = "ID du stock") @PathParam("id") UUID id) {
|
||||
logger.info("DELETE /stocks/{}", id);
|
||||
try {
|
||||
stockService.delete(id);
|
||||
return Response.noContent().build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression du stock: {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la suppression du stock"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.TacheTemplateService;
|
||||
import dev.lions.btpxpress.domain.core.entity.SousPhaseTemplate;
|
||||
import dev.lions.btpxpress.domain.core.entity.TacheTemplate;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeChantierBTP;
|
||||
import dev.lions.btpxpress.domain.infrastructure.repository.SousPhaseTemplateRepository;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des templates de tâches BTP Permet aux utilisateurs de gérer
|
||||
* complètement les tâches après déploiement Fournit les opérations CRUD pour la gestion granulaire
|
||||
* des tâches
|
||||
*/
|
||||
@Path("/api/v1/tache-templates")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Tâche Templates", description = "Gestion granulaire des templates de tâches BTP")
|
||||
public class TacheTemplateResource {
|
||||
|
||||
@Inject TacheTemplateService tacheTemplateService;
|
||||
|
||||
@Inject SousPhaseTemplateRepository sousPhaseTemplateRepository;
|
||||
|
||||
// ===================================
|
||||
// CONSULTATION DES TÂCHES TEMPLATES
|
||||
// ===================================
|
||||
|
||||
/** Récupère toutes les tâches d'une sous-phase */
|
||||
@GET
|
||||
@Path("/by-sous-phase/{sousPhaseId}")
|
||||
@Operation(summary = "Récupère toutes les tâches d'une sous-phase template")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches pour la sous-phase template")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getTachesBySousPhase(
|
||||
@Parameter(description = "ID de la sous-phase template parent") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId) {
|
||||
|
||||
// Vérifier que la sous-phase template existe
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<TacheTemplate> taches = tacheTemplateService.getTachesBySousPhase(sousPhaseId);
|
||||
return Response.ok(taches).build();
|
||||
}
|
||||
|
||||
/** Récupère toutes les tâches actives d'une sous-phase */
|
||||
@GET
|
||||
@Path("/by-sous-phase/{sousPhaseId}/actives")
|
||||
@Operation(summary = "Récupère toutes les tâches actives d'une sous-phase template")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches actives")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getActiveTachesBySousPhase(
|
||||
@Parameter(description = "ID de la sous-phase template") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<TacheTemplate> taches = tacheTemplateService.getActiveTachesBySousPhase(sousPhaseId);
|
||||
return Response.ok(taches).build();
|
||||
}
|
||||
|
||||
/** Récupère une tâche template par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère une tâche template par son ID")
|
||||
@APIResponse(responseCode = "200", description = "Tâche template trouvée")
|
||||
@APIResponse(responseCode = "404", description = "Tâche template non trouvée")
|
||||
public Response getTacheTemplate(
|
||||
@Parameter(description = "Identifiant de la tâche template") @PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
TacheTemplate tache = tacheTemplateService.getTacheTemplateById(id);
|
||||
return Response.ok(tache).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Tâche template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Recherche des tâches par nom ou description */
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Recherche de tâches par nom ou description")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches correspondantes")
|
||||
@APIResponse(responseCode = "400", description = "Paramètre de recherche manquant")
|
||||
public Response searchTaches(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String searchTerm) {
|
||||
|
||||
if (searchTerm == null || searchTerm.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Le terme de recherche est obligatoire")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<TacheTemplate> taches = tacheTemplateService.searchTaches(searchTerm.trim());
|
||||
return Response.ok(taches).build();
|
||||
}
|
||||
|
||||
/** Récupère toutes les tâches d'un type de chantier */
|
||||
@GET
|
||||
@Path("/by-type-chantier/{typeChantier}")
|
||||
@Operation(summary = "Récupère toutes les tâches d'un type de chantier")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches pour le type de chantier")
|
||||
@APIResponse(responseCode = "400", description = "Type de chantier invalide")
|
||||
public Response getTachesByTypeChantier(
|
||||
@Parameter(description = "Type de chantier BTP") @PathParam("typeChantier")
|
||||
String typeChantierStr) {
|
||||
|
||||
try {
|
||||
TypeChantierBTP typeChantier = TypeChantierBTP.valueOf(typeChantierStr.toUpperCase());
|
||||
List<TacheTemplate> taches = tacheTemplateService.getTachesByTypeChantier(typeChantier);
|
||||
return Response.ok(taches).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Type de chantier invalide: " + typeChantierStr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les statistiques d'une sous-phase basées sur ses tâches */
|
||||
@GET
|
||||
@Path("/stats/by-sous-phase/{sousPhaseId}")
|
||||
@Operation(summary = "Calcule les statistiques d'une sous-phase basées sur ses tâches")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques de la sous-phase")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getSousPhaseStatistics(
|
||||
@Parameter(description = "ID de la sous-phase template") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
TacheTemplateService.SousPhaseStatistics stats =
|
||||
tacheTemplateService.calculateSousPhaseStatistics(sousPhaseId);
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
/** Récupère toutes les tâches critiques d'une sous-phase */
|
||||
@GET
|
||||
@Path("/critiques/by-sous-phase/{sousPhaseId}")
|
||||
@Operation(summary = "Récupère toutes les tâches critiques d'une sous-phase")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches critiques")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getCriticalTachesBySousPhase(
|
||||
@Parameter(description = "ID de la sous-phase template") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<TacheTemplate> taches = tacheTemplateService.getCriticalTachesBySousPhase(sousPhaseId);
|
||||
return Response.ok(taches).build();
|
||||
}
|
||||
|
||||
/** Récupère toutes les tâches bloquantes d'une sous-phase */
|
||||
@GET
|
||||
@Path("/bloquantes/by-sous-phase/{sousPhaseId}")
|
||||
@Operation(summary = "Récupère toutes les tâches bloquantes d'une sous-phase")
|
||||
@APIResponse(responseCode = "200", description = "Liste des tâches bloquantes")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response getBlockingTachesBySousPhase(
|
||||
@Parameter(description = "ID de la sous-phase template") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId) {
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
List<TacheTemplate> taches = tacheTemplateService.getBlockingTachesBySousPhase(sousPhaseId);
|
||||
return Response.ok(taches).build();
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// ADMINISTRATION DES TÂCHES TEMPLATES
|
||||
// ===================================
|
||||
|
||||
/** Crée une nouvelle tâche template */
|
||||
@POST
|
||||
@Operation(summary = "Crée une nouvelle tâche template")
|
||||
@APIResponse(responseCode = "201", description = "Tâche template créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template parent non trouvée")
|
||||
public Response createTacheTemplate(@Valid TacheTemplate tacheTemplate) {
|
||||
|
||||
// Vérifier que la sous-phase template parent existe
|
||||
if (tacheTemplate.getSousPhaseParent() == null
|
||||
|| tacheTemplate.getSousPhaseParent().getId() == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("La sous-phase template parent est obligatoire")
|
||||
.build();
|
||||
}
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate =
|
||||
sousPhaseTemplateRepository.findById(tacheTemplate.getSousPhaseParent().getId());
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template parent non trouvée")
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
TacheTemplate createdTache = tacheTemplateService.createTacheTemplate(tacheTemplate);
|
||||
return Response.status(Response.Status.CREATED).entity(createdTache).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour une tâche template */
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour une tâche template")
|
||||
@APIResponse(responseCode = "200", description = "Tâche template mise à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Tâche template non trouvée")
|
||||
public Response updateTacheTemplate(
|
||||
@Parameter(description = "Identifiant de la tâche template") @PathParam("id") UUID id,
|
||||
@Valid TacheTemplate tacheTemplateData) {
|
||||
|
||||
try {
|
||||
TacheTemplate updatedTache = tacheTemplateService.updateTacheTemplate(id, tacheTemplateData);
|
||||
return Response.ok(updatedTache).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().contains("non trouvé")) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Duplique une tâche template vers une autre sous-phase */
|
||||
@POST
|
||||
@Path("/{id}/duplicate")
|
||||
@Operation(summary = "Duplique une tâche template vers une autre sous-phase")
|
||||
@APIResponse(responseCode = "201", description = "Tâche template dupliquée avec succès")
|
||||
@APIResponse(
|
||||
responseCode = "404",
|
||||
description = "Tâche template ou sous-phase de destination non trouvée")
|
||||
public Response duplicateTacheTemplate(
|
||||
@Parameter(description = "ID de la tâche template à dupliquer") @PathParam("id") UUID id,
|
||||
@Parameter(description = "ID de la nouvelle sous-phase parent") @QueryParam("newSousPhaseId")
|
||||
UUID newSousPhaseId) {
|
||||
|
||||
if (newSousPhaseId == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("L'ID de la nouvelle sous-phase est obligatoire")
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
TacheTemplate duplicatedTache =
|
||||
tacheTemplateService.duplicateTacheTemplate(id, newSousPhaseId);
|
||||
return Response.status(Response.Status.CREATED).entity(duplicatedTache).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Désactive une tâche template */
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
@Operation(summary = "Désactive une tâche template")
|
||||
@APIResponse(responseCode = "204", description = "Tâche template désactivée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Tâche template non trouvée")
|
||||
public Response deactivateTacheTemplate(
|
||||
@Parameter(description = "Identifiant de la tâche template") @PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
tacheTemplateService.deactivateTacheTemplate(id);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Tâche template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime définitivement une tâche template */
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime définitivement une tâche template")
|
||||
@APIResponse(responseCode = "204", description = "Tâche template supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Tâche template non trouvée")
|
||||
public Response deleteTacheTemplate(
|
||||
@Parameter(description = "Identifiant de la tâche template") @PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
tacheTemplateService.deleteTacheTemplate(id);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Tâche template non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Réorganise l'ordre des tâches dans une sous-phase */
|
||||
@PUT
|
||||
@Path("/reorder/by-sous-phase/{sousPhaseId}")
|
||||
@Operation(summary = "Réorganise l'ordre des tâches dans une sous-phase")
|
||||
@APIResponse(responseCode = "200", description = "Tâches réorganisées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Liste des IDs invalide")
|
||||
@APIResponse(responseCode = "404", description = "Sous-phase template non trouvée")
|
||||
public Response reorderTaches(
|
||||
@Parameter(description = "ID de la sous-phase template") @PathParam("sousPhaseId")
|
||||
UUID sousPhaseId,
|
||||
List<UUID> tacheIds) {
|
||||
|
||||
if (tacheIds == null || tacheIds.isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("La liste des IDs de tâches ne peut pas être vide")
|
||||
.build();
|
||||
}
|
||||
|
||||
SousPhaseTemplate sousPhaseTemplate = sousPhaseTemplateRepository.findById(sousPhaseId);
|
||||
if (sousPhaseTemplate == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Sous-phase template non trouvée avec l'ID: " + sousPhaseId)
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
tacheTemplateService.reorderTaches(sousPhaseId, tacheIds);
|
||||
return Response.ok().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Crée plusieurs tâches en lot */
|
||||
@POST
|
||||
@Path("/batch")
|
||||
@Operation(summary = "Crée plusieurs tâches templates en lot")
|
||||
@APIResponse(responseCode = "201", description = "Tâches créées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createTachesBatch(@Valid List<TacheTemplate> taches) {
|
||||
|
||||
if (taches == null || taches.isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("La liste des tâches ne peut pas être vide")
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
List<TacheTemplate> createdTaches =
|
||||
taches.stream().map(tacheTemplateService::createTacheTemplate).toList();
|
||||
return Response.status(Response.Status.CREATED).entity(createdTaches).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Erreur lors de la création des tâches: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère un template vide pour la création */
|
||||
@GET
|
||||
@Path("/template-vide")
|
||||
@Operation(summary = "Récupère un template vide pour la création d'une nouvelle tâche")
|
||||
@APIResponse(responseCode = "200", description = "Template vide")
|
||||
public Response getEmptyTemplate() {
|
||||
TacheTemplate emptyTemplate = new TacheTemplate();
|
||||
emptyTemplate.setNombreOperateursRequis(1);
|
||||
emptyTemplate.setCritique(false);
|
||||
emptyTemplate.setBloquante(false);
|
||||
emptyTemplate.setActif(true);
|
||||
emptyTemplate.setConditionsMeteo(TacheTemplate.ConditionMeteo.TOUS_TEMPS);
|
||||
return Response.ok(emptyTemplate).build();
|
||||
}
|
||||
|
||||
/** Validation des données d'une tâche template */
|
||||
@POST
|
||||
@Path("/validate")
|
||||
@Operation(summary = "Valide les données d'une tâche template")
|
||||
@APIResponse(responseCode = "200", description = "Résultat de validation")
|
||||
public Response validateTacheTemplate(TacheTemplate tacheTemplate) {
|
||||
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.valid = true;
|
||||
|
||||
if (tacheTemplate.getNom() == null || tacheTemplate.getNom().trim().isEmpty()) {
|
||||
result.valid = false;
|
||||
result.errors.add("Le nom de la tâche est obligatoire");
|
||||
}
|
||||
|
||||
if (tacheTemplate.getSousPhaseParent() == null) {
|
||||
result.valid = false;
|
||||
result.errors.add("La sous-phase parente est obligatoire");
|
||||
}
|
||||
|
||||
if (tacheTemplate.getNombreOperateursRequis() != null
|
||||
&& tacheTemplate.getNombreOperateursRequis() < 1) {
|
||||
result.valid = false;
|
||||
result.errors.add("Le nombre d'opérateurs requis doit être au moins 1");
|
||||
}
|
||||
|
||||
if (tacheTemplate.getDureeEstimeeMinutes() != null
|
||||
&& tacheTemplate.getDureeEstimeeMinutes() < 1) {
|
||||
result.valid = false;
|
||||
result.errors.add("La durée estimée doit être au moins 1 minute");
|
||||
}
|
||||
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
/** Classe pour le résultat de validation */
|
||||
public static class ValidationResult {
|
||||
public boolean valid;
|
||||
public List<String> errors = new java.util.ArrayList<>();
|
||||
}
|
||||
}
|
||||
435
src/main/java/dev/lions/btpxpress/adapter/http/UserResource.java
Normal file
435
src/main/java/dev/lions/btpxpress/adapter/http/UserResource.java
Normal file
@@ -0,0 +1,435 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.UserService;
|
||||
import dev.lions.btpxpress.domain.core.entity.User;
|
||||
import dev.lions.btpxpress.domain.core.entity.UserRole;
|
||||
import dev.lions.btpxpress.domain.core.entity.UserStatus;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* API REST pour la gestion des utilisateurs BTP
|
||||
* Expose les fonctionnalités de création, consultation et administration des utilisateurs
|
||||
*/
|
||||
@Path("/api/v1/users")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Utilisateurs", description = "Gestion des utilisateurs du système")
|
||||
@SecurityRequirement(name = "JWT")
|
||||
// @Authenticated - Désactivé pour les tests
|
||||
public class UserResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserResource.class);
|
||||
|
||||
@Inject UserService userService;
|
||||
|
||||
// ===================================
|
||||
// CONSULTATION DES UTILISATEURS
|
||||
// ===================================
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupère tous les utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des utilisateurs")
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response getAllUsers(
|
||||
@Parameter(description = "Numéro de page (0-indexed)") @QueryParam("page") @DefaultValue("0") int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size,
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search,
|
||||
@Parameter(description = "Filtrer par rôle") @QueryParam("role") String role,
|
||||
@Parameter(description = "Filtrer par statut") @QueryParam("status") String status) {
|
||||
try {
|
||||
List<User> users;
|
||||
|
||||
if (search != null && !search.isEmpty()) {
|
||||
users = userService.searchUsers(search, page, size);
|
||||
} else if (role != null && !role.isEmpty()) {
|
||||
UserRole userRole = UserRole.valueOf(role.toUpperCase());
|
||||
users = userService.findByRole(userRole, page, size);
|
||||
} else if (status != null && !status.isEmpty()) {
|
||||
UserStatus userStatus = UserStatus.valueOf(status.toUpperCase());
|
||||
users = userService.findByStatus(userStatus, page, size);
|
||||
} else {
|
||||
users = userService.findAll(page, size);
|
||||
}
|
||||
|
||||
// Convertir en DTO pour éviter d'exposer les données sensibles
|
||||
List<UserResponse> userResponses = users.stream().map(this::toUserResponse).toList();
|
||||
|
||||
return Response.ok(userResponses).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des utilisateurs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des utilisateurs"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère un utilisateur par ID")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response getUserById(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
return userService
|
||||
.findById(userId)
|
||||
.map(user -> Response.ok(toUserResponse(user)).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Utilisateur non trouvé avec l'ID: " + id))
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "ID d'utilisateur invalide: " + id))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre d'utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Nombre d'utilisateurs retourné avec succès")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response countUsers(
|
||||
@Parameter(description = "Filtrer par statut") @QueryParam("status") String status,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
try {
|
||||
long count;
|
||||
if (status != null && !status.isEmpty()) {
|
||||
UserStatus userStatus = UserStatus.valueOf(status.toUpperCase());
|
||||
count = userService.countByStatus(userStatus);
|
||||
} else {
|
||||
count = userService.count();
|
||||
}
|
||||
|
||||
return Response.ok(new CountResponse(count)).build();
|
||||
} catch (SecurityException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity("Accès refusé: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du comptage des utilisateurs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du comptage des utilisateurs: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/pending")
|
||||
@Operation(summary = "Récupérer les utilisateurs en attente de validation")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des utilisateurs en attente récupérée avec succès")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response getPendingUsers(
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
try {
|
||||
List<User> pendingUsers = userService.findByStatus(UserStatus.PENDING, 0, 100);
|
||||
List<UserResponse> userResponses = pendingUsers.stream().map(this::toUserResponse).toList();
|
||||
|
||||
return Response.ok(userResponses).build();
|
||||
} catch (SecurityException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity("Accès refusé: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des utilisateurs en attente", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des utilisateurs en attente: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Récupère les statistiques des utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques des utilisateurs")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response getUserStats() {
|
||||
try {
|
||||
Object stats = userService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques utilisateurs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la génération des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// GESTION DES UTILISATEURS
|
||||
// ===================================
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Crée un nouvel utilisateur")
|
||||
@APIResponse(responseCode = "201", description = "Utilisateur créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Email déjà utilisé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response createUser(
|
||||
@Parameter(description = "Données du nouvel utilisateur") @Valid @NotNull CreateUserRequest request) {
|
||||
try {
|
||||
User user = userService.createUser(
|
||||
request.email,
|
||||
request.password,
|
||||
request.nom,
|
||||
request.prenom,
|
||||
request.role,
|
||||
request.status);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(toUserResponse(user)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création de l'utilisateur", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la création de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response updateUser(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouvelles données utilisateur") @Valid @NotNull UpdateUserRequest request) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
User user = userService.updateUser(userId, request.nom, request.prenom, request.email);
|
||||
|
||||
return Response.ok(toUserResponse(user)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la modification de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la modification de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/status")
|
||||
@Operation(summary = "Met à jour le statut d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Statut mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response updateUserStatus(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouveau statut") @Valid @NotNull UpdateStatusRequest request) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
UserStatus status = UserStatus.valueOf(request.status.toUpperCase());
|
||||
|
||||
User user = userService.updateStatus(userId, status);
|
||||
|
||||
return Response.ok(toUserResponse(user)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Statut invalide: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la modification du statut utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la modification du statut"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/role")
|
||||
@Operation(summary = "Met à jour le rôle d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Rôle mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Rôle invalide")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response updateUserRole(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouveau rôle") @Valid @NotNull UpdateRoleRequest request) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
UserRole role = UserRole.valueOf(request.role.toUpperCase());
|
||||
|
||||
User user = userService.updateRole(userId, role);
|
||||
|
||||
return Response.ok(toUserResponse(user)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Rôle invalide: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la modification du rôle utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la modification du rôle"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/approve")
|
||||
@Operation(summary = "Approuve un utilisateur en attente")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur approuvé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response approveUser(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
User user = userService.approveUser(userId);
|
||||
|
||||
return Response.ok(toUserResponse(user)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'approbation de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'approbation de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reject")
|
||||
@Operation(summary = "Rejette un utilisateur en attente")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur rejeté avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response rejectUser(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id,
|
||||
@Parameter(description = "Raison du rejet") @Valid @NotNull RejectUserRequest request) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
userService.rejectUser(userId, request.reason);
|
||||
|
||||
return Response.ok(Map.of("message", "Utilisateur rejeté avec succès")).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du rejet de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du rejet de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime un utilisateur")
|
||||
@APIResponse(responseCode = "204", description = "Utilisateur supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur non trouvé")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response deleteUser(
|
||||
@Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
userService.deleteUser(userId);
|
||||
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "ID invalide: " + e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la suppression de l'utilisateur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
private UserResponse toUserResponse(User user) {
|
||||
return new UserResponse(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getNom(),
|
||||
user.getPrenom(),
|
||||
user.getRole().toString(),
|
||||
user.getStatus().toString(),
|
||||
user.getDateCreation(),
|
||||
user.getDateModification(),
|
||||
user.getDerniereConnexion(),
|
||||
user.getActif());
|
||||
}
|
||||
|
||||
// === CLASSES UTILITAIRES ===
|
||||
|
||||
public static record CountResponse(long count) {}
|
||||
|
||||
public static record CreateUserRequest(
|
||||
@Parameter(description = "Email de l'utilisateur") String email,
|
||||
@Parameter(description = "Mot de passe") String password,
|
||||
@Parameter(description = "Nom de famille") String nom,
|
||||
@Parameter(description = "Prénom") String prenom,
|
||||
@Parameter(description = "Rôle (USER, ADMIN, MANAGER)") String role,
|
||||
@Parameter(description = "Statut (ACTIF, INACTIF, SUSPENDU)") String status) {}
|
||||
|
||||
public static record UpdateUserRequest(
|
||||
@Parameter(description = "Nouveau nom") String nom,
|
||||
@Parameter(description = "Nouveau prénom") String prenom,
|
||||
@Parameter(description = "Nouvel email") String email) {}
|
||||
|
||||
public static record UpdateStatusRequest(
|
||||
@Parameter(description = "Nouveau statut") String status) {}
|
||||
|
||||
public static record UpdateRoleRequest(@Parameter(description = "Nouveau rôle") String role) {}
|
||||
|
||||
public static record RejectUserRequest(
|
||||
@Parameter(description = "Raison du rejet") String reason) {}
|
||||
|
||||
public static record UserResponse(
|
||||
UUID id,
|
||||
String email,
|
||||
String nom,
|
||||
String prenom,
|
||||
String role,
|
||||
String status,
|
||||
LocalDateTime dateCreation,
|
||||
LocalDateTime dateModification,
|
||||
LocalDateTime derniereConnexion,
|
||||
Boolean actif) {}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.ZoneClimatiqueService;
|
||||
import dev.lions.btpxpress.domain.core.entity.ZoneClimatique;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des zones climatiques africaines
|
||||
* Architecture 2025 : API complète pour la gestion des contraintes climatiques de construction
|
||||
*/
|
||||
@Path("/api/v1/zones-climatiques")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Zones Climatiques", description = "Gestion des zones climatiques et contraintes BTP")
|
||||
public class ZoneClimatiqueResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZoneClimatiqueResource.class);
|
||||
|
||||
@Inject ZoneClimatiqueService zoneClimatiqueService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Récupérer toutes les zones climatiques actives",
|
||||
description = "Retourne la liste complète des zones climatiques actives")
|
||||
@APIResponse(responseCode = "200", description = "Liste des zones climatiques récupérée avec succès")
|
||||
public Response getAllZones() {
|
||||
logger.debug("GET /zones-climatiques");
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findAll();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/all")
|
||||
@Operation(
|
||||
summary = "Récupérer toutes les zones (actives et inactives)",
|
||||
description = "Retourne la liste complète incluant les zones désactivées")
|
||||
@APIResponse(responseCode = "200", description = "Liste complète des zones climatiques")
|
||||
public Response getAllZonesIncludingInactive() {
|
||||
logger.debug("Récupération de toutes les zones (actives et inactives)");
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findAllIncludingInactive();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer une zone par ID", description = "Retourne les détails d'une zone climatique")
|
||||
@APIResponse(responseCode = "200", description = "Zone climatique trouvée")
|
||||
@APIResponse(responseCode = "404", description = "Zone climatique non trouvée")
|
||||
public Response getZoneById(@Parameter(description = "ID de la zone climatique") @PathParam("id") Long id) {
|
||||
logger.debug("GET /zones-climatiques/{}", id);
|
||||
ZoneClimatique zone = zoneClimatiqueService.findById(id);
|
||||
return Response.ok(zone).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/code/{code}")
|
||||
@Operation(
|
||||
summary = "Récupérer une zone par code",
|
||||
description = "Retourne les détails d'une zone climatique par son code unique")
|
||||
@APIResponse(responseCode = "200", description = "Zone climatique trouvée")
|
||||
@APIResponse(responseCode = "404", description = "Zone climatique non trouvée")
|
||||
public Response getZoneByCode(@PathParam("code") String code) {
|
||||
logger.debug("Récupération de la zone climatique avec code: {}", code);
|
||||
ZoneClimatique zone = zoneClimatiqueService.findByCode(code);
|
||||
return Response.ok(zone).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/temperature-range")
|
||||
@Operation(
|
||||
summary = "Rechercher par plage de température",
|
||||
description = "Retourne les zones dont la température moyenne est dans la plage spécifiée")
|
||||
@APIResponse(responseCode = "200", description = "Zones trouvées")
|
||||
public Response getByTemperatureRange(
|
||||
@Parameter(description = "Température minimale (°C)", example = "15")
|
||||
@QueryParam("min")
|
||||
BigDecimal min,
|
||||
@Parameter(description = "Température maximale (°C)", example = "40")
|
||||
@QueryParam("max")
|
||||
BigDecimal max) {
|
||||
logger.debug("Recherche zones climatiques par température: {} - {}", min, max);
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findByTemperatureRange(min, max);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/pluviometrie")
|
||||
@Operation(
|
||||
summary = "Rechercher par pluviométrie",
|
||||
description = "Retourne les zones dont la pluviométrie annuelle est dans la plage spécifiée")
|
||||
@APIResponse(responseCode = "200", description = "Zones trouvées")
|
||||
public Response getByPluviometrie(
|
||||
@Parameter(description = "Pluviométrie minimale (mm)", example = "500") @QueryParam("min")
|
||||
Integer min,
|
||||
@Parameter(description = "Pluviométrie maximale (mm)", example = "2000") @QueryParam("max")
|
||||
Integer max) {
|
||||
logger.debug("Recherche zones climatiques par pluviométrie: {} - {}", min, max);
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findByPluviometrie(min, max);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/risque-seisme")
|
||||
@Operation(
|
||||
summary = "Zones avec risque sismique",
|
||||
description = "Retourne toutes les zones présentant un risque sismique")
|
||||
@APIResponse(responseCode = "200", description = "Zones sismiques trouvées")
|
||||
public Response getWithSeismicRisk() {
|
||||
logger.debug("Recherche zones avec risque sismique");
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findAvecRisqueSeisme();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/risque-cyclones")
|
||||
@Operation(
|
||||
summary = "Zones avec risque cyclonique",
|
||||
description = "Retourne toutes les zones présentant un risque cyclonique")
|
||||
@APIResponse(responseCode = "200", description = "Zones cycloniques trouvées")
|
||||
public Response getWithCycloneRisk() {
|
||||
logger.debug("Recherche zones avec risque cyclonique");
|
||||
List<ZoneClimatique> zones = zoneClimatiqueService.findAvecRisqueCyclones();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(
|
||||
summary = "Recherche avancée",
|
||||
description = "Recherche avancée avec critères multiples")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
|
||||
public Response search(
|
||||
@QueryParam("tempMin") BigDecimal tempMin,
|
||||
@QueryParam("tempMax") BigDecimal tempMax,
|
||||
@QueryParam("pluvioMin") Integer pluvioMin,
|
||||
@QueryParam("pluvioMax") Integer pluvioMax,
|
||||
@QueryParam("risqueSeisme") Boolean risqueSeisme,
|
||||
@QueryParam("corrosionMarine") Boolean corrosionMarine,
|
||||
@QueryParam("texte") String texte) {
|
||||
logger.debug(
|
||||
"Recherche avancée - temp: {}-{}, pluvio: {}-{}, seisme: {}, corrosion: {}, texte: {}",
|
||||
tempMin,
|
||||
tempMax,
|
||||
pluvioMin,
|
||||
pluvioMax,
|
||||
risqueSeisme,
|
||||
corrosionMarine,
|
||||
texte);
|
||||
List<ZoneClimatique> zones =
|
||||
zoneClimatiqueService.search(
|
||||
tempMin, tempMax, pluvioMin, pluvioMax, risqueSeisme, corrosionMarine, texte);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("zones", zones);
|
||||
response.put("total", zones.size());
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Statistiques des zones climatiques",
|
||||
description = "Retourne des statistiques globales sur les zones climatiques")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques calculées")
|
||||
public Response getStatistics() {
|
||||
logger.debug("Récupération des statistiques des zones climatiques");
|
||||
Map<String, Object> stats = zoneClimatiqueService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
// =================== ENDPOINTS DE CRÉATION ===================
|
||||
|
||||
@POST
|
||||
@Authenticated
|
||||
@Operation(summary = "Créer une nouvelle zone climatique", description = "Crée une nouvelle zone climatique")
|
||||
@APIResponse(responseCode = "201", description = "Zone climatique créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides ou code déjà existant")
|
||||
public Response createZone(@Valid @NotNull ZoneClimatique zone) {
|
||||
logger.info("Création d'une nouvelle zone climatique: {}", zone.getCode());
|
||||
ZoneClimatique created = zoneClimatiqueService.create(zone);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MODIFICATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Modifier une zone climatique", description = "Met à jour les informations d'une zone")
|
||||
@APIResponse(responseCode = "200", description = "Zone modifiée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Zone non trouvée")
|
||||
public Response updateZone(@PathParam("id") Long id, @Valid @NotNull ZoneClimatique zone) {
|
||||
logger.info("PUT /zones-climatiques/{} - Modification", id);
|
||||
ZoneClimatique updated = zoneClimatiqueService.update(id, zone);
|
||||
return Response.ok(updated).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activate")
|
||||
@Authenticated
|
||||
@Operation(summary = "Activer une zone climatique", description = "Réactive une zone désactivée")
|
||||
@APIResponse(responseCode = "200", description = "Zone activée avec succès")
|
||||
public Response activateZone(@PathParam("id") Long id) {
|
||||
logger.info("Activation de la zone climatique ID: {}", id);
|
||||
zoneClimatiqueService.activate(id);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("message", "Zone climatique activée avec succès");
|
||||
response.put("id", id);
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
@Authenticated
|
||||
@Operation(summary = "Désactiver une zone climatique", description = "Désactive une zone")
|
||||
@APIResponse(responseCode = "200", description = "Zone désactivée avec succès")
|
||||
public Response deactivateZone(@PathParam("id") Long id) {
|
||||
logger.info("Désactivation de la zone climatique ID: {}", id);
|
||||
zoneClimatiqueService.deactivate(id);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("message", "Zone climatique désactivée avec succès");
|
||||
response.put("id", id);
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
// === ENDPOINT DE SUPPRESSION - ARCHITECTURE 2025 ===
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Authenticated
|
||||
@Operation(summary = "Supprimer une zone climatique", description = "Supprime définitivement une zone")
|
||||
@APIResponse(responseCode = "204", description = "Zone supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Zone non trouvée")
|
||||
public Response deleteZone(@PathParam("id") Long id) {
|
||||
logger.info("DELETE /zones-climatiques/{} - Suppression", id);
|
||||
zoneClimatiqueService.delete(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user