package dev.lions.btpxpress.adapter.http; import dev.lions.btpxpress.application.service.ChantierService; import dev.lions.btpxpress.domain.core.entity.Chantier; import dev.lions.btpxpress.domain.core.entity.StatutChantier; import dev.lions.btpxpress.domain.shared.dto.ChantierCreateDTO; 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.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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Resource REST pour la gestion des chantiers - Architecture 2025 MIGRATION: Préservation exacte de * tous les endpoints critiques */ @Path("/api/v1/chantiers") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Chantiers", description = "Gestion des chantiers BTP") // @Authenticated - Désactivé pour les tests public class ChantierResource { private static final Logger logger = LoggerFactory.getLogger(ChantierResource.class); @Inject ChantierService chantierService; // === ENDPOINTS DE CONSULTATION - API CONTRACTS PRÉSERVÉS EXACTEMENT === @GET @Operation(summary = "Récupérer tous les chantiers") @APIResponse(responseCode = "200", description = "Liste des chantiers récupérée avec succès") public Response getAllChantiers( @Parameter(description = "Terme de recherche") @QueryParam("search") String search, @Parameter(description = "Statut du chantier") @QueryParam("statut") String statut, @Parameter(description = "ID du client") @QueryParam("clientId") String clientId) { try { List chantiers; if (clientId != null && !clientId.isEmpty()) { chantiers = chantierService.findByClient(UUID.fromString(clientId)); } else if (statut != null && !statut.isEmpty()) { chantiers = chantierService.findByStatut(StatutChantier.valueOf(statut.toUpperCase())); } else if (search != null && !search.isEmpty()) { chantiers = chantierService.search(search); } else { chantiers = chantierService.findAll(); } return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers: " + e.getMessage()) .build(); } } @GET @Path("/actifs") @Operation(summary = "Récupérer tous les chantiers actifs") @APIResponse( responseCode = "200", description = "Liste des chantiers actifs récupérée avec succès") public Response getAllActiveChantiers() { try { List chantiers = chantierService.findAllActive(); return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers actifs", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers actifs: " + e.getMessage()) .build(); } } @GET @Path("/{id}") @Operation(summary = "Récupérer un chantier par ID") @APIResponse(responseCode = "200", description = "Chantier récupéré avec succès") @APIResponse(responseCode = "404", description = "Chantier non trouvé") public Response getChantierById( @Parameter(description = "ID du chantier") @PathParam("id") String id) { try { UUID chantierId = UUID.fromString(id); return chantierService .findById(chantierId) .map(chantier -> Response.ok(chantier).build()) .orElse( Response.status(Response.Status.NOT_FOUND) .entity("Chantier non trouvé avec l'ID: " + id) .build()); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity("ID de chantier invalide: " + id) .build(); } catch (Exception e) { logger.error("Erreur lors de la récupération du chantier {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération du chantier: " + e.getMessage()) .build(); } } @GET @Path("/count") @Operation(summary = "Compter le nombre de chantiers") @APIResponse(responseCode = "200", description = "Nombre de chantiers récupéré avec succès") public Response countChantiers() { try { long count = chantierService.count(); return Response.ok(new CountResponse(count)).build(); } catch (Exception e) { logger.error("Erreur lors du comptage des chantiers", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors du comptage des chantiers: " + e.getMessage()) .build(); } } @GET @Path("/stats") @Operation(summary = "Obtenir les statistiques des chantiers") @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès") public Response getStats() { try { Object stats = chantierService.getStatistics(); return Response.ok(stats).build(); } catch (Exception e) { logger.error("Erreur lors de la génération des statistiques des chantiers", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la génération des statistiques: " + e.getMessage()) .build(); } } @GET @Path("/statut/{statut}") public Response getChantiersByStatut(@PathParam("statut") String statut) { try { StatutChantier statutEnum = StatutChantier.valueOf(statut.toUpperCase()); List chantiers = chantierService.findByStatut(statutEnum); return Response.ok(chantiers).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity( "Statut invalide: " + statut + ". Valeurs possibles: PLANIFIE, EN_COURS, TERMINE, ANNULE, SUSPENDU") .build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers par statut {}", statut, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers: " + e.getMessage()) .build(); } } @GET @Path("/en-cours") public Response getChantiersEnCours() { try { List chantiers = chantierService.findEnCours(); return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers en cours", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers en cours: " + e.getMessage()) .build(); } } @GET @Path("/planifies") public Response getChantiersPlanifies() { try { List chantiers = chantierService.findPlanifies(); return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers planifiés", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers planifiés: " + e.getMessage()) .build(); } } @GET @Path("/termines") public Response getChantiersTermines() { try { List chantiers = chantierService.findTermines(); return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des chantiers terminés", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des chantiers terminés: " + e.getMessage()) .build(); } } // === ENDPOINTS DE GESTION - API CONTRACTS PRÉSERVÉS EXACTEMENT === @POST @Operation(summary = "Créer un nouveau chantier") @APIResponse(responseCode = "201", description = "Chantier créé avec succès") @APIResponse(responseCode = "400", description = "Données invalides") public Response createChantier( @Parameter(description = "Données du chantier à créer") @Valid @NotNull ChantierCreateDTO chantierDTO) { try { Chantier chantier = chantierService.create(chantierDTO); return Response.status(Response.Status.CREATED).entity(chantier).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity("Données invalides: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors de la création du chantier", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la création du chantier: " + e.getMessage()) .build(); } } @PUT @Path("/{id}") @Operation(summary = "Mettre à jour un chantier") @APIResponse(responseCode = "200", description = "Chantier mis à jour avec succès") @APIResponse(responseCode = "400", description = "Données invalides") @APIResponse(responseCode = "404", description = "Chantier non trouvé") public Response updateChantier( @Parameter(description = "ID du chantier") @PathParam("id") String id, @Parameter(description = "Nouvelles données du chantier") @Valid @NotNull ChantierCreateDTO chantierDTO) { try { UUID chantierId = UUID.fromString(id); Chantier chantier = chantierService.update(chantierId, chantierDTO); return Response.ok(chantier).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity("Données invalides: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors de la mise à jour du chantier {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la mise à jour du chantier: " + e.getMessage()) .build(); } } @PUT @Path("/{id}/statut") public Response updateChantierStatut(@PathParam("id") String id, UpdateStatutRequest request) { try { UUID chantierId = UUID.fromString(id); StatutChantier nouveauStatut = StatutChantier.valueOf(request.statut.toUpperCase()); Chantier chantier = chantierService.updateStatut(chantierId, nouveauStatut); return Response.ok(chantier).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity("Données invalides: " + e.getMessage()) .build(); } catch (IllegalStateException e) { return Response.status(Response.Status.CONFLICT) .entity("Transition de statut non autorisée: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors de la mise à jour du statut du chantier {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la mise à jour du statut: " + e.getMessage()) .build(); } } @DELETE @Path("/{id}") @Operation(summary = "Supprimer un chantier") @APIResponse(responseCode = "204", description = "Chantier supprimé avec succès") @APIResponse(responseCode = "400", description = "ID invalide") @APIResponse(responseCode = "404", description = "Chantier non trouvé") @APIResponse(responseCode = "409", description = "Impossible de supprimer") public Response deleteChantier( @Parameter(description = "ID du chantier") @PathParam("id") String id, @Parameter(description = "Suppression définitive (true) ou logique (false, défaut)") @QueryParam("permanent") @DefaultValue("false") boolean permanent) { try { UUID chantierId = UUID.fromString(id); if (permanent) { chantierService.deletePhysically(chantierId); } else { chantierService.delete(chantierId); } return Response.noContent().build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity("ID invalide: " + e.getMessage()) .build(); } catch (IllegalStateException e) { return Response.status(Response.Status.CONFLICT) .entity("Impossible de supprimer: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors de la suppression du chantier {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la suppression du chantier: " + e.getMessage()) .build(); } } // =========================================== // ENDPOINTS DE RECHERCHE AVANCÉE // =========================================== @GET @Path("/date-range") public Response getChantiersByDateRange( @QueryParam("dateDebut") String dateDebut, @QueryParam("dateFin") String dateFin) { try { if (dateDebut == null || dateFin == null) { return Response.status(Response.Status.BAD_REQUEST) .entity("Les paramètres dateDebut et dateFin sont obligatoires") .build(); } LocalDate debut = LocalDate.parse(dateDebut); LocalDate fin = LocalDate.parse(dateFin); if (debut.isAfter(fin)) { return Response.status(Response.Status.BAD_REQUEST) .entity("La date de début ne peut pas être après la date de fin") .build(); } List chantiers = chantierService.findByDateRange(debut, fin); return Response.ok(chantiers).build(); } catch (Exception e) { logger.error("Erreur lors de la recherche par plage de dates", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la recherche: " + e.getMessage()) .build(); } } // =========================================== // CLASSES UTILITAIRES // =========================================== public static record CountResponse(long count) {} public static record UpdateStatutRequest(String statut) {} }