Initial commit
This commit is contained in:
258
src/main/java/dev/lions/btpxpress/adapter/http/AuthResource.java
Normal file
258
src/main/java/dev/lions/btpxpress/adapter/http/AuthResource.java
Normal file
@@ -0,0 +1,258 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
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.security.Principal;
|
||||
import java.util.Map;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
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 l'authentification et les informations utilisateur
|
||||
* Permet de récupérer les informations de l'utilisateur connecté depuis le token JWT Keycloak
|
||||
*/
|
||||
@Path("/api/v1/auth")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Authentification", description = "Gestion de l'authentification et informations utilisateur")
|
||||
public class AuthResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthResource.class);
|
||||
|
||||
@Inject
|
||||
JsonWebToken jwt;
|
||||
|
||||
/**
|
||||
* Récupère les informations de l'utilisateur connecté depuis le token JWT
|
||||
*/
|
||||
@GET
|
||||
@Path("/user")
|
||||
@PermitAll // Accessible même sans authentification pour les tests
|
||||
@Operation(
|
||||
summary = "Informations utilisateur connecté",
|
||||
description = "Récupère les informations de l'utilisateur connecté depuis le token JWT Keycloak")
|
||||
@APIResponse(responseCode = "200", description = "Informations utilisateur récupérées")
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié")
|
||||
public Response getCurrentUser(@Context SecurityContext securityContext) {
|
||||
try {
|
||||
logger.debug("Récupération des informations utilisateur connecté");
|
||||
|
||||
// Vérifier si l'utilisateur est authentifié
|
||||
Principal principal = securityContext.getUserPrincipal();
|
||||
|
||||
if (principal == null || jwt == null) {
|
||||
logger.warn("Aucun utilisateur authentifié trouvé");
|
||||
|
||||
// En mode développement, retourner un utilisateur de test
|
||||
return Response.ok(createTestUser()).build();
|
||||
}
|
||||
|
||||
// Extraire les informations du token JWT
|
||||
String userId = jwt.getSubject();
|
||||
String username = jwt.getClaim("preferred_username");
|
||||
String email = jwt.getClaim("email");
|
||||
String firstName = jwt.getClaim("given_name");
|
||||
String lastName = jwt.getClaim("family_name");
|
||||
String fullName = jwt.getClaim("name");
|
||||
|
||||
// Extraire les rôles
|
||||
Object realmAccess = jwt.getClaim("realm_access");
|
||||
Object resourceAccess = jwt.getClaim("resource_access");
|
||||
|
||||
// Construire la réponse avec les informations utilisateur
|
||||
Map<String, Object> userInfo = new java.util.HashMap<>();
|
||||
userInfo.put("id", userId != null ? userId : "unknown");
|
||||
userInfo.put("username", username != null ? username : email);
|
||||
userInfo.put("email", email != null ? email : "unknown@btpxpress.com");
|
||||
userInfo.put("firstName", firstName != null ? firstName : "Utilisateur");
|
||||
userInfo.put("lastName", lastName != null ? lastName : "Connecté");
|
||||
userInfo.put("fullName", fullName != null ? fullName : (firstName + " " + lastName).trim());
|
||||
userInfo.put("roles", extractRoles(realmAccess, resourceAccess));
|
||||
userInfo.put("permissions", extractPermissions(realmAccess, resourceAccess));
|
||||
userInfo.put("isAdmin", isAdmin(realmAccess, resourceAccess));
|
||||
userInfo.put("isManager", isManager(realmAccess, resourceAccess));
|
||||
userInfo.put("isEmployee", isEmployee(realmAccess, resourceAccess));
|
||||
userInfo.put("isClient", isClient(realmAccess, resourceAccess));
|
||||
|
||||
logger.info("Informations utilisateur récupérées: {} ({})", username, email);
|
||||
return Response.ok(userInfo).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des informations utilisateur", e);
|
||||
|
||||
// En cas d'erreur, retourner un utilisateur de test en mode développement
|
||||
return Response.ok(createTestUser()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint de test pour vérifier l'état de l'authentification
|
||||
*/
|
||||
@GET
|
||||
@Path("/status")
|
||||
@PermitAll
|
||||
@Operation(
|
||||
summary = "Statut d'authentification",
|
||||
description = "Vérifie l'état de l'authentification de l'utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Statut récupéré")
|
||||
public Response getAuthStatus(@Context SecurityContext securityContext) {
|
||||
try {
|
||||
Principal principal = securityContext.getUserPrincipal();
|
||||
boolean isAuthenticated = principal != null && jwt != null;
|
||||
|
||||
Map<String, Object> status = Map.of(
|
||||
"authenticated", isAuthenticated,
|
||||
"principal", principal != null ? principal.getName() : null,
|
||||
"hasJWT", jwt != null,
|
||||
"timestamp", System.currentTimeMillis()
|
||||
);
|
||||
|
||||
return Response.ok(status).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la vérification du statut d'authentification", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la vérification du statut"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un utilisateur de test pour le mode développement
|
||||
*/
|
||||
private Map<String, Object> createTestUser() {
|
||||
Map<String, Object> testUser = new java.util.HashMap<>();
|
||||
testUser.put("id", "dev-user-001");
|
||||
testUser.put("username", "admin.btpxpress");
|
||||
testUser.put("email", "admin@btpxpress.com");
|
||||
testUser.put("firstName", "Jean-Michel");
|
||||
testUser.put("lastName", "Martineau");
|
||||
testUser.put("fullName", "Jean-Michel Martineau");
|
||||
testUser.put("roles", java.util.List.of("SUPER_ADMIN", "ADMIN", "DIRECTEUR"));
|
||||
testUser.put("permissions", java.util.List.of("ALL_PERMISSIONS"));
|
||||
testUser.put("isAdmin", true);
|
||||
testUser.put("isManager", true);
|
||||
testUser.put("isEmployee", false);
|
||||
testUser.put("isClient", false);
|
||||
return testUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les rôles depuis les claims JWT
|
||||
*/
|
||||
private java.util.List<String> extractRoles(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = new java.util.ArrayList<>();
|
||||
|
||||
// Ajouter les rôles du realm
|
||||
if (realmAccess instanceof Map) {
|
||||
Object realmRoles = ((Map<?, ?>) realmAccess).get("roles");
|
||||
if (realmRoles instanceof java.util.List) {
|
||||
((java.util.List<?>) realmRoles).forEach(role -> {
|
||||
if (role instanceof String) {
|
||||
roles.add((String) role);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les rôles des ressources
|
||||
if (resourceAccess instanceof Map) {
|
||||
((Map<?, ?>) resourceAccess).values().forEach(resource -> {
|
||||
if (resource instanceof Map) {
|
||||
Object resourceRoles = ((Map<?, ?>) resource).get("roles");
|
||||
if (resourceRoles instanceof java.util.List) {
|
||||
((java.util.List<?>) resourceRoles).forEach(role -> {
|
||||
if (role instanceof String) {
|
||||
roles.add((String) role);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return roles.isEmpty() ? java.util.List.of("USER") : roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les permissions depuis les rôles
|
||||
*/
|
||||
private java.util.List<String> extractPermissions(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = extractRoles(realmAccess, resourceAccess);
|
||||
java.util.List<String> permissions = new java.util.ArrayList<>();
|
||||
|
||||
// Mapper les rôles vers les permissions
|
||||
for (String role : roles) {
|
||||
switch (role.toUpperCase()) {
|
||||
case "SUPER_ADMIN":
|
||||
case "BTPXPRESS_SUPER_ADMIN":
|
||||
permissions.add("ALL_PERMISSIONS");
|
||||
break;
|
||||
case "ADMIN":
|
||||
case "BTPXPRESS_ADMIN":
|
||||
permissions.addAll(java.util.List.of("MANAGE_USERS", "MANAGE_CHANTIERS", "MANAGE_CLIENTS"));
|
||||
break;
|
||||
case "DIRECTEUR":
|
||||
case "MANAGER":
|
||||
permissions.addAll(java.util.List.of("VIEW_REPORTS", "MANAGE_CHANTIERS"));
|
||||
break;
|
||||
case "CHEF_CHANTIER":
|
||||
permissions.addAll(java.util.List.of("MANAGE_CHANTIER", "VIEW_PLANNING"));
|
||||
break;
|
||||
default:
|
||||
permissions.add("VIEW_BASIC");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return permissions.isEmpty() ? java.util.List.of("VIEW_BASIC") : permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est administrateur
|
||||
*/
|
||||
private boolean isAdmin(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = extractRoles(realmAccess, resourceAccess);
|
||||
return roles.stream().anyMatch(role ->
|
||||
role.toUpperCase().contains("ADMIN") || role.toUpperCase().contains("SUPER_ADMIN"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est manager
|
||||
*/
|
||||
private boolean isManager(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = extractRoles(realmAccess, resourceAccess);
|
||||
return roles.stream().anyMatch(role ->
|
||||
role.toUpperCase().contains("DIRECTEUR") ||
|
||||
role.toUpperCase().contains("MANAGER") ||
|
||||
role.toUpperCase().contains("CHEF"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est employé
|
||||
*/
|
||||
private boolean isEmployee(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = extractRoles(realmAccess, resourceAccess);
|
||||
return roles.stream().anyMatch(role ->
|
||||
role.toUpperCase().contains("EMPLOYE") ||
|
||||
role.toUpperCase().contains("OUVRIER"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est client
|
||||
*/
|
||||
private boolean isClient(Object realmAccess, Object resourceAccess) {
|
||||
java.util.List<String> roles = extractRoles(realmAccess, resourceAccess);
|
||||
return roles.stream().anyMatch(role ->
|
||||
role.toUpperCase().contains("CLIENT"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.BudgetService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Budget;
|
||||
import dev.lions.btpxpress.domain.core.entity.Budget.StatutBudget;
|
||||
import dev.lions.btpxpress.domain.core.entity.Budget.TendanceBudget;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
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 budgets - Architecture 2025 Endpoints pour le suivi budgétaire
|
||||
* des chantiers
|
||||
*/
|
||||
@Path("/api/v1/budgets")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Budgets", description = "Gestion du suivi budgétaire")
|
||||
public class BudgetResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BudgetResource.class);
|
||||
|
||||
@Inject BudgetService budgetService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les budgets")
|
||||
@APIResponse(responseCode = "200", description = "Liste des budgets récupérée avec succès")
|
||||
public Response getAllBudgets(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search,
|
||||
@Parameter(description = "Statut du budget") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Tendance du budget") @QueryParam("tendance") String tendance) {
|
||||
try {
|
||||
List<Budget> budgets;
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
budgets = budgetService.findByStatut(StatutBudget.valueOf(statut.toUpperCase()));
|
||||
} else if (tendance != null && !tendance.isEmpty()) {
|
||||
budgets = budgetService.findByTendance(TendanceBudget.valueOf(tendance.toUpperCase()));
|
||||
} else if (search != null && !search.isEmpty()) {
|
||||
budgets = budgetService.search(search);
|
||||
} else {
|
||||
budgets = budgetService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(budgets).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des budgets", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des budgets")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un budget par son ID")
|
||||
@APIResponse(responseCode = "200", description = "Budget récupéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response getBudgetById(@PathParam("id") UUID id) {
|
||||
try {
|
||||
return budgetService
|
||||
.findById(id)
|
||||
.map(budget -> Response.ok(budget).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du budget")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
@Operation(summary = "Récupérer le budget d'un chantier")
|
||||
@APIResponse(responseCode = "200", description = "Budget du chantier récupéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé pour ce chantier")
|
||||
public Response getBudgetByChantier(@PathParam("chantierId") UUID chantierId) {
|
||||
try {
|
||||
return budgetService
|
||||
.findByChantier(chantierId)
|
||||
.map(budget -> Response.ok(budget).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du budget pour le chantier {}", chantierId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du budget")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/depassement")
|
||||
@Operation(summary = "Récupérer les budgets en dépassement")
|
||||
@APIResponse(responseCode = "200", description = "Budgets en dépassement récupérés avec succès")
|
||||
public Response getBudgetsEnDepassement() {
|
||||
try {
|
||||
List<Budget> budgets = budgetService.findEnDepassement();
|
||||
return Response.ok(budgets).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des budgets en dépassement", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des budgets")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/attention")
|
||||
@Operation(summary = "Récupérer les budgets nécessitant une attention")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Budgets nécessitant attention récupérés avec succès")
|
||||
public Response getBudgetsNecessitantAttention() {
|
||||
try {
|
||||
List<Budget> budgets = budgetService.findNecessitantAttention();
|
||||
return Response.ok(budgets).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des budgets nécessitant attention", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des budgets")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupérer les statistiques globales des budgets")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
Map<String, Object> stats = budgetService.getStatistiquesGlobales();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du calcul des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du calcul des statistiques")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer un nouveau budget")
|
||||
@APIResponse(responseCode = "201", description = "Budget créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createBudget(Budget budget) {
|
||||
try {
|
||||
budgetService.validerBudget(budget);
|
||||
Budget nouveauBudget = budgetService.create(budget);
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauBudget).build();
|
||||
} catch (BadRequestException e) {
|
||||
logger.warn(
|
||||
"Tentative de création d'un budget avec des données invalides: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du budget", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création du budget")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un budget")
|
||||
@APIResponse(responseCode = "200", description = "Budget mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateBudget(@PathParam("id") UUID id, Budget budget) {
|
||||
try {
|
||||
budgetService.validerBudget(budget);
|
||||
Budget budgetMisAJour = budgetService.update(id, budget);
|
||||
return Response.ok(budgetMisAJour).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (BadRequestException e) {
|
||||
logger.warn(
|
||||
"Tentative de mise à jour d'un budget avec des données invalides: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour du budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour du budget")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer un budget")
|
||||
@APIResponse(responseCode = "204", description = "Budget supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response deleteBudget(@PathParam("id") UUID id) {
|
||||
try {
|
||||
budgetService.delete(id);
|
||||
return Response.noContent().build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression du budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression du budget")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS MÉTIER ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/depenses")
|
||||
@Operation(summary = "Mettre à jour les dépenses d'un budget")
|
||||
@APIResponse(responseCode = "200", description = "Dépenses mises à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response updateDepenses(
|
||||
@PathParam("id") UUID id, @Parameter(description = "Nouvelle dépense") BigDecimal depense) {
|
||||
try {
|
||||
Budget budget = budgetService.mettreAJourDepenses(id, depense);
|
||||
return Response.ok(budget).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 des dépenses pour le budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour des dépenses")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/avancement")
|
||||
@Operation(summary = "Mettre à jour l'avancement d'un budget")
|
||||
@APIResponse(responseCode = "200", description = "Avancement mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response updateAvancement(
|
||||
@PathParam("id") UUID id,
|
||||
@Parameter(description = "Nouvel avancement") BigDecimal avancement) {
|
||||
try {
|
||||
Budget budget = budgetService.mettreAJourAvancement(id, avancement);
|
||||
return Response.ok(budget).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 l'avancement pour le budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de l'avancement")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/alertes")
|
||||
@Operation(summary = "Ajouter une alerte à un budget")
|
||||
@APIResponse(responseCode = "200", description = "Alerte ajoutée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response ajouterAlerte(
|
||||
@PathParam("id") UUID id,
|
||||
@Parameter(description = "Description de l'alerte") String description) {
|
||||
try {
|
||||
budgetService.ajouterAlerte(id, description);
|
||||
return Response.ok().build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'ajout d'alerte pour le budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'ajout de l'alerte")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}/alertes")
|
||||
@Operation(summary = "Supprimer les alertes d'un budget")
|
||||
@APIResponse(responseCode = "200", description = "Alertes supprimées avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Budget non trouvé")
|
||||
public Response supprimerAlertes(@PathParam("id") UUID id) {
|
||||
try {
|
||||
budgetService.supprimerAlertes(id);
|
||||
return Response.ok().build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression des alertes pour le budget {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression des alertes")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.CalculateurTechniqueBTP;
|
||||
import dev.lions.btpxpress.application.service.CalculateurTechniqueBTP.*;
|
||||
import dev.lions.btpxpress.domain.core.entity.MaterielBTP;
|
||||
import dev.lions.btpxpress.domain.core.entity.ZoneClimatique;
|
||||
import dev.lions.btpxpress.domain.infrastructure.repository.MaterielBTPRepository;
|
||||
import dev.lions.btpxpress.domain.infrastructure.repository.ZoneClimatiqueRepository;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* API REST pour les calculs techniques ultra-détaillés BTP Le plus ambitieux système de calculs BTP
|
||||
* d'Afrique
|
||||
*/
|
||||
@Path("/api/v1/calculs-techniques")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(
|
||||
name = "CalculsTechniques",
|
||||
description = "Calculs techniques ultra-détaillés BTP - Système le plus avancé d'Afrique")
|
||||
public class CalculsTechniquesResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CalculsTechniquesResource.class);
|
||||
|
||||
@Inject CalculateurTechniqueBTP calculateur;
|
||||
|
||||
@Inject MaterielBTPRepository materielRepository;
|
||||
|
||||
@Inject ZoneClimatiqueRepository zoneClimatiqueRepository;
|
||||
|
||||
// =================== CALCULS MAÇONNERIE ===================
|
||||
|
||||
@POST
|
||||
@Path("/briques-mur")
|
||||
@Operation(summary = "Calcul ultra-précis quantité briques pour mur")
|
||||
@APIResponse(responseCode = "200", description = "Calcul réussi avec détails complets")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "404", description = "Matériau ou zone climatique non trouvée")
|
||||
public Response calculerBriquesMur(
|
||||
@Parameter(description = "Paramètres détaillés du calcul") @Valid @NotNull
|
||||
ParametresCalculBriques params) {
|
||||
|
||||
try {
|
||||
logger.info(
|
||||
"🧮 Début calcul briques - Surface: {}m², Zone: {}",
|
||||
params.surface,
|
||||
params.zoneClimatique);
|
||||
|
||||
// Validation paramètres
|
||||
if (params.surface == null || params.surface.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Surface doit être > 0"))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (params.epaisseurMur == null || params.epaisseurMur.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Épaisseur mur doit être > 0"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Appel service calcul
|
||||
ResultatCalculBriques resultat = calculateur.calculerBriquesMur(params);
|
||||
|
||||
logger.info("✅ Calcul briques terminé - {} briques nécessaires", resultat.nombreBriques);
|
||||
|
||||
return Response.ok(resultat).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("❌ Erreur paramètres calcul briques: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("💥 Erreur inattendue calcul briques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne lors du calcul"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/mortier-maconnerie")
|
||||
@Operation(summary = "Calcul mortier pour maçonnerie traditionnelle")
|
||||
@APIResponse(responseCode = "200", description = "Quantités mortier calculées")
|
||||
public Response calculerMortierMaconnerie(
|
||||
@Parameter(description = "Paramètres calcul mortier") @Valid @NotNull
|
||||
ParametresCalculMortier params) {
|
||||
|
||||
try {
|
||||
// Validation
|
||||
if (params.volumeMaconnerie == null
|
||||
|| params.volumeMaconnerie.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Volume maçonnerie requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Calcul volume mortier (environ 20-25% du volume maçonnerie)
|
||||
BigDecimal volumeMortier = params.volumeMaconnerie.multiply(new BigDecimal("0.23"));
|
||||
|
||||
// Dosage mortier selon usage
|
||||
String dosage = params.typeMortier != null ? params.typeMortier : "STANDARD";
|
||||
int cimentKgM3 =
|
||||
switch (dosage) {
|
||||
case "POSE_BRIQUES" -> 350;
|
||||
case "JOINTOIEMENT" -> 450;
|
||||
case "ENDUIT_BASE" -> 300;
|
||||
case "ENDUIT_FINITION" -> 400;
|
||||
default -> 350; // STANDARD
|
||||
};
|
||||
|
||||
int cimentTotal = volumeMortier.multiply(new BigDecimal(cimentKgM3)).intValue();
|
||||
int sableTotal = volumeMortier.multiply(new BigDecimal("800")).intValue(); // 800L/m³
|
||||
int eauTotal = volumeMortier.multiply(new BigDecimal("175")).intValue(); // 175L/m³
|
||||
|
||||
ResultatCalculMortier resultat = new ResultatCalculMortier();
|
||||
resultat.volumeTotal = volumeMortier;
|
||||
resultat.cimentKg = cimentTotal;
|
||||
resultat.sableLitres = sableTotal;
|
||||
resultat.eauLitres = eauTotal;
|
||||
resultat.sacs50kg = (int) Math.ceil(cimentTotal / 50.0);
|
||||
|
||||
return Response.ok(resultat).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur calcul mortier", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur calcul mortier"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// =================== CALCULS BÉTON ARMÉ ===================
|
||||
|
||||
@POST
|
||||
@Path("/beton-arme")
|
||||
@Operation(summary = "Calcul béton armé avec adaptation climatique africaine")
|
||||
@APIResponse(responseCode = "200", description = "Calcul complet béton + armatures + adaptations")
|
||||
public Response calculerBetonArme(
|
||||
@Parameter(description = "Paramètres béton armé détaillés") @Valid @NotNull
|
||||
ParametresCalculBetonArme params) {
|
||||
|
||||
try {
|
||||
logger.info(
|
||||
"🏗️ Calcul béton armé - Vol: {}m³, Classe: {}", params.volume, params.classeBeton);
|
||||
|
||||
// Validations
|
||||
if (params.volume == null || params.volume.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Volume béton requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (params.classeBeton == null || params.classeBeton.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Classe béton requise"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Appel service calcul
|
||||
ResultatCalculBetonArme resultat = calculateur.calculerBetonArme(params);
|
||||
|
||||
logger.info(
|
||||
"✅ Béton calculé - {} sacs ciment, {} kg acier",
|
||||
resultat.cimentSacs50kg,
|
||||
resultat.acierKgTotal);
|
||||
|
||||
return Response.ok(resultat).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur calcul béton armé", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur calcul béton armé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/dosages-beton")
|
||||
@Operation(summary = "Liste des dosages béton standard avec adaptations climatiques")
|
||||
@APIResponse(responseCode = "200", description = "Dosages disponibles")
|
||||
public Response getDosagesBeton() {
|
||||
|
||||
Map<String, Object> dosages =
|
||||
Map.of(
|
||||
"C20/25",
|
||||
Map.of(
|
||||
"usage", "Béton de propreté, fondations simples",
|
||||
"ciment", "300 kg/m³",
|
||||
"resistance", "20 MPa caractéristique",
|
||||
"exposition", "XC1 - Intérieur sec"),
|
||||
"C25/30",
|
||||
Map.of(
|
||||
"usage", "Dalles, poutres courantes, ouvrages courants",
|
||||
"ciment", "350 kg/m³",
|
||||
"resistance", "25 MPa caractéristique",
|
||||
"exposition", "XC3 - Intérieur humide"),
|
||||
"C30/37",
|
||||
Map.of(
|
||||
"usage", "Béton armé, précontraint, ouvrages d'art",
|
||||
"ciment", "385 kg/m³",
|
||||
"resistance", "30 MPa caractéristique",
|
||||
"exposition", "XC4 - Extérieur avec gel/dégel"),
|
||||
"C35/45",
|
||||
Map.of(
|
||||
"usage", "Béton haute performance, ouvrages spéciaux",
|
||||
"ciment", "420 kg/m³",
|
||||
"resistance", "35 MPa caractéristique",
|
||||
"exposition", "XS1/XS3 - Environnement marin"));
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"dosages",
|
||||
dosages,
|
||||
"notes",
|
||||
List.of(
|
||||
"Dosages adaptés climat tropical africain",
|
||||
"Majoration ciment en zone très chaude (+25kg/m³)",
|
||||
"Réduction E/C en zone marine (-10L/m³)",
|
||||
"Cure renforcée obligatoire (7j minimum)")))
|
||||
.build();
|
||||
}
|
||||
|
||||
// =================== INFORMATIONS MATÉRIAUX ===================
|
||||
|
||||
@GET
|
||||
@Path("/materiaux")
|
||||
@Operation(summary = "Liste des matériaux BTP avec spécifications détaillées")
|
||||
@APIResponse(responseCode = "200", description = "Catalogue matériaux ultra-détaillé")
|
||||
public Response getMateriaux(
|
||||
@QueryParam("categorie") String categorie, @QueryParam("zone") String zoneClimatique) {
|
||||
|
||||
try {
|
||||
List<MaterielBTP> materiaux;
|
||||
|
||||
if (categorie != null && !categorie.trim().isEmpty()) {
|
||||
MaterielBTP.CategorieMateriel cat = MaterielBTP.CategorieMateriel.valueOf(categorie);
|
||||
materiaux = materielRepository.findByCategorie(cat);
|
||||
} else {
|
||||
materiaux = materielRepository.findAllActifs();
|
||||
}
|
||||
|
||||
// Filtrage par zone climatique si spécifiée
|
||||
if (zoneClimatique != null && !zoneClimatique.trim().isEmpty()) {
|
||||
ZoneClimatique zone = zoneClimatiqueRepository.findByCode(zoneClimatique).orElse(null);
|
||||
if (zone != null) {
|
||||
materiaux = materiaux.stream().filter(m -> zone.isMaterielAdapte(m)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"materiaux", materiaux,
|
||||
"total", materiaux.size(),
|
||||
"filtres",
|
||||
Map.of(
|
||||
"categorie", categorie != null ? categorie : "TOUTES",
|
||||
"zone", zoneClimatique != null ? zoneClimatique : "TOUTES")))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur récupération matériaux", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur récupération matériaux"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/zones-climatiques")
|
||||
@Operation(summary = "Zones climatiques africaines avec contraintes construction")
|
||||
@APIResponse(responseCode = "200", description = "Zones climatiques détaillées")
|
||||
public Response getZonesClimatiques() {
|
||||
|
||||
try {
|
||||
List<ZoneClimatique> zones = zoneClimatiqueRepository.findAllActives();
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"zones",
|
||||
zones,
|
||||
"info",
|
||||
"Zones climatiques spécialisées pour l'Afrique avec contraintes construction"
|
||||
+ " détaillées"))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur zones climatiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur zones climatiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// =================== CLASSES DTO ===================
|
||||
|
||||
public static class ParametresCalculMortier {
|
||||
public BigDecimal volumeMaconnerie;
|
||||
public String typeMortier; // POSE_BRIQUES, JOINTOIEMENT, ENDUIT_BASE, ENDUIT_FINITION
|
||||
public String zoneClimatique;
|
||||
}
|
||||
|
||||
// [AUTRES CLASSES DTO DÉJÀ DÉFINIES DANS CalculateurTechniqueBTP...]
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
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<Chantier> 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<Chantier> 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<Chantier> 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<Chantier> 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<Chantier> 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<Chantier> 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<Chantier> 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) {}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.ClientService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Client;
|
||||
import dev.lions.btpxpress.domain.core.entity.Permission;
|
||||
import dev.lions.btpxpress.domain.shared.dto.ClientCreateDTO;
|
||||
import dev.lions.btpxpress.infrastructure.security.RequirePermission;
|
||||
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.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 clients - Architecture 2025 MIGRATION: Préservation exacte de
|
||||
* toutes les API endpoints et contrats
|
||||
*/
|
||||
@Path("/api/v1/clients")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Clients", description = "Gestion des clients")
|
||||
// @Authenticated - Désactivé pour les tests
|
||||
public class ClientResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientResource.class);
|
||||
|
||||
@Inject ClientService clientService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@RequirePermission(Permission.CLIENTS_READ)
|
||||
@Operation(summary = "Récupérer tous les clients")
|
||||
@APIResponse(responseCode = "200", description = "Liste des clients récupérée avec succès")
|
||||
public Response getAllClients(
|
||||
@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 /clients - page: {}, size: {}", page, size);
|
||||
|
||||
List<Client> clients;
|
||||
if (page == 0 && size == 20) {
|
||||
clients = clientService.findAll();
|
||||
} else {
|
||||
clients = clientService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(clients).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@RequirePermission(Permission.CLIENTS_READ)
|
||||
@Operation(summary = "Récupérer un client par ID")
|
||||
@APIResponse(responseCode = "200", description = "Client trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Client non trouvé")
|
||||
public Response getClientById(@Parameter(description = "ID du client") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("GET /clients/{}", id);
|
||||
|
||||
Client client = clientService.findByIdRequired(id);
|
||||
return Response.ok(client).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Rechercher des clients")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
public Response searchClients(
|
||||
@Parameter(description = "Nom du client") @QueryParam("nom") String nom,
|
||||
@Parameter(description = "Entreprise") @QueryParam("entreprise") String entreprise,
|
||||
@Parameter(description = "Ville") @QueryParam("ville") String ville,
|
||||
@Parameter(description = "Email") @QueryParam("email") String email) {
|
||||
|
||||
logger.debug(
|
||||
"GET /clients/search - nom: {}, entreprise: {}, ville: {}, email: {}",
|
||||
nom,
|
||||
entreprise,
|
||||
ville,
|
||||
email);
|
||||
|
||||
List<Client> clients;
|
||||
|
||||
// Logique de recherche exacte préservée
|
||||
if (email != null && !email.trim().isEmpty()) {
|
||||
clients = clientService.findByEmail(email).map(List::of).orElse(List.of());
|
||||
} else if (nom != null && !nom.trim().isEmpty()) {
|
||||
clients = clientService.searchByNom(nom);
|
||||
} else if (entreprise != null && !entreprise.trim().isEmpty()) {
|
||||
clients = clientService.searchByEntreprise(entreprise);
|
||||
} else if (ville != null && !ville.trim().isEmpty()) {
|
||||
clients = clientService.searchByVille(ville);
|
||||
} else {
|
||||
clients = clientService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(clients).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'ÉCRITURE - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@POST
|
||||
@RequirePermission(Permission.CLIENTS_CREATE)
|
||||
@Operation(summary = "Créer un nouveau client")
|
||||
@APIResponse(responseCode = "201", description = "Client créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createClient(@Valid @NotNull ClientCreateDTO clientDTO) {
|
||||
logger.debug("POST /clients");
|
||||
logger.info(
|
||||
"Données reçues: nom={}, prenom={}, email={}",
|
||||
clientDTO.getNom(),
|
||||
clientDTO.getPrenom(),
|
||||
clientDTO.getEmail());
|
||||
|
||||
try {
|
||||
Client createdClient = clientService.createFromDTO(clientDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(createdClient).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du client: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@RequirePermission(Permission.CLIENTS_UPDATE)
|
||||
@Operation(summary = "Mettre à jour un client")
|
||||
@APIResponse(responseCode = "200", description = "Client mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Client non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateClient(
|
||||
@Parameter(description = "ID du client") @PathParam("id") UUID id,
|
||||
@Valid @NotNull Client client) {
|
||||
|
||||
logger.debug("PUT /clients/{}", id);
|
||||
|
||||
Client updatedClient = clientService.update(id, client);
|
||||
return Response.ok(updatedClient).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@RequirePermission(Permission.CLIENTS_DELETE)
|
||||
@Operation(summary = "Supprimer un client")
|
||||
@APIResponse(responseCode = "204", description = "Client supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Client non trouvé")
|
||||
public Response deleteClient(@Parameter(description = "ID du client") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("DELETE /clients/{}", id);
|
||||
|
||||
clientService.delete(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre de clients")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de clients")
|
||||
public Response countClients() {
|
||||
logger.debug("GET /clients/count");
|
||||
|
||||
long count = clientService.count();
|
||||
return Response.ok(count).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.*;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
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 le tableau de bord - Architecture 2025 DASHBOARD: API de métriques et
|
||||
* indicateurs BTP
|
||||
*/
|
||||
@Path("/api/v1/dashboard")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Dashboard", description = "Tableau de bord et métriques BTP")
|
||||
public class DashboardResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DashboardResource.class);
|
||||
|
||||
@Inject ChantierService chantierService;
|
||||
|
||||
@Inject EquipeService equipeService;
|
||||
|
||||
@Inject EmployeService employeService;
|
||||
|
||||
@Inject MaterielService materielService;
|
||||
|
||||
@Inject MaintenanceService maintenanceService;
|
||||
|
||||
@Inject DocumentService documentService;
|
||||
|
||||
@Inject DisponibiliteService disponibiliteService;
|
||||
|
||||
@Inject PlanningService planningService;
|
||||
|
||||
// === DASHBOARD PRINCIPAL ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Tableau de bord principal",
|
||||
description = "Récupère les métriques principales du système BTP")
|
||||
@APIResponse(responseCode = "200", description = "Métriques du dashboard récupérées")
|
||||
public Response getDashboardPrincipal() {
|
||||
logger.debug("Génération du dashboard principal");
|
||||
|
||||
// Métriques globales
|
||||
final long totalChantiers = chantierService.count();
|
||||
final long chantiersEnCours = chantierService.countByStatut(StatutChantier.EN_COURS);
|
||||
final long chantiersPlanifies = chantierService.countByStatut(StatutChantier.PLANIFIE);
|
||||
final long chantiersActifs = chantiersEnCours + chantiersPlanifies;
|
||||
final long totalEquipes = equipeService.count();
|
||||
final long equipesDisponibles = equipeService.countByStatut(StatutEquipe.DISPONIBLE);
|
||||
final long totalEmployes = employeService.count();
|
||||
final long employesActifs = employeService.countActifs();
|
||||
final long totalMateriel = materielService.count();
|
||||
final long materielDisponible = materielService.countDisponible();
|
||||
|
||||
// Métriques de maintenance
|
||||
final long maintenancesEnRetard = maintenanceService.findEnRetard().size();
|
||||
final long maintenancesPlanifiees = maintenanceService.findPlanifiees().size();
|
||||
|
||||
// Métriques de planning
|
||||
final List<PlanningEvent> evenementsAujourdhui =
|
||||
planningService.findEventsByDateRange(LocalDate.now(), LocalDate.now());
|
||||
final long evenementsAujourdhui_count = evenementsAujourdhui.size();
|
||||
|
||||
// Métriques de documents
|
||||
final long totalDocuments = documentService.findAll().size();
|
||||
final List<Document> documentsRecents = documentService.findRecents(5);
|
||||
|
||||
// Disponibilités en attente
|
||||
final long disponibilitesEnAttenteCount = disponibiliteService.findEnAttente().size();
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final Object chantiers =
|
||||
new Object() {
|
||||
public final long total = totalChantiers;
|
||||
public final long actifs = chantiersActifs;
|
||||
public final double tauxActivite =
|
||||
totalChantiers > 0 ? (double) chantiersActifs / totalChantiers * 100 : 0;
|
||||
};
|
||||
|
||||
public final Object equipes =
|
||||
new Object() {
|
||||
public final long total = totalEquipes;
|
||||
public final long disponibles = equipesDisponibles;
|
||||
public final double tauxDisponibilite =
|
||||
totalEquipes > 0 ? (double) equipesDisponibles / totalEquipes * 100 : 0;
|
||||
};
|
||||
|
||||
public final Object employes =
|
||||
new Object() {
|
||||
public final long total = totalEmployes;
|
||||
public final long actifs = employesActifs;
|
||||
public final double tauxActivite =
|
||||
totalEmployes > 0 ? (double) employesActifs / totalEmployes * 100 : 0;
|
||||
};
|
||||
|
||||
public final Object materiel =
|
||||
new Object() {
|
||||
public final long total = totalMateriel;
|
||||
public final long disponible = materielDisponible;
|
||||
public final double tauxDisponibilite =
|
||||
totalMateriel > 0 ? (double) materielDisponible / totalMateriel * 100 : 0;
|
||||
};
|
||||
|
||||
public final Object maintenance =
|
||||
new Object() {
|
||||
public final long enRetard = maintenancesEnRetard;
|
||||
public final long planifiees = maintenancesPlanifiees;
|
||||
public final boolean alerteRetard = maintenancesEnRetard > 0;
|
||||
};
|
||||
|
||||
public final Object planning =
|
||||
new Object() {
|
||||
public final long evenementsAujourdhui = evenementsAujourdhui_count;
|
||||
public final long disponibilitesEnAttente = disponibilitesEnAttenteCount;
|
||||
};
|
||||
|
||||
public final Object documents =
|
||||
new Object() {
|
||||
public final long total = totalDocuments;
|
||||
public final List<Object> recents =
|
||||
documentsRecents.stream()
|
||||
.map(
|
||||
doc ->
|
||||
new Object() {
|
||||
public final UUID id = doc.getId();
|
||||
public final String nom = doc.getNom();
|
||||
public final String type = doc.getTypeDocument().toString();
|
||||
public final LocalDateTime dateCreation =
|
||||
doc.getDateCreation();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final LocalDateTime derniereMAJ = LocalDateTime.now();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === DASHBOARDS SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/chantiers")
|
||||
@Operation(
|
||||
summary = "Dashboard des chantiers",
|
||||
description = "Métriques détaillées des chantiers")
|
||||
@APIResponse(responseCode = "200", description = "Métriques des chantiers récupérées")
|
||||
public Response getDashboardChantiers() {
|
||||
logger.debug("Génération du dashboard chantiers");
|
||||
|
||||
final Object statistiquesChantiers = chantierService.getStatistics();
|
||||
// Afficher tous les chantiers actifs (EN_COURS et PLANIFIE)
|
||||
final List<Chantier> chantiersEnCours = chantierService.findByStatut(StatutChantier.EN_COURS);
|
||||
final List<Chantier> chantiersPlanifies = chantierService.findByStatut(StatutChantier.PLANIFIE);
|
||||
final List<Chantier> chantiersActivesListe = new java.util.ArrayList<>();
|
||||
chantiersActivesListe.addAll(chantiersEnCours);
|
||||
chantiersActivesListe.addAll(chantiersPlanifies);
|
||||
|
||||
// Chantiers en retard = chantiers dont la date de fin prévue est dépassée
|
||||
final List<Chantier> chantiersEnRetardListe =
|
||||
chantiersActivesListe.stream()
|
||||
.filter(
|
||||
c -> c.getDateFinPrevue() != null && c.getDateFinPrevue().isBefore(LocalDate.now()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final Object statistiques = statistiquesChantiers;
|
||||
public final List<Object> chantiersActifs =
|
||||
chantiersActivesListe.stream()
|
||||
.map(
|
||||
chantier ->
|
||||
new Object() {
|
||||
public final UUID id = chantier.getId();
|
||||
public final String nom = chantier.getNom();
|
||||
public final String adresse = chantier.getAdresse();
|
||||
public final LocalDate dateDebut = chantier.getDateDebut();
|
||||
public final LocalDate dateFinPrevue = chantier.getDateFinPrevue();
|
||||
public final String statut = chantier.getStatut().toString();
|
||||
public final String client =
|
||||
chantier.getClient() != null
|
||||
? chantier.getClient().getPrenom()
|
||||
+ " "
|
||||
+ chantier.getClient().getNom()
|
||||
: "Non assigné";
|
||||
public final double budget =
|
||||
chantier.getMontantContrat().doubleValue();
|
||||
public final double coutReel = chantier.getCoutReel().doubleValue();
|
||||
public final int avancement =
|
||||
(int) chantier.getPourcentageAvancement();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final List<Object> chantiersEnRetard =
|
||||
chantiersEnRetardListe.stream()
|
||||
.map(
|
||||
chantier ->
|
||||
new Object() {
|
||||
public final UUID id = chantier.getId();
|
||||
public final String nom = chantier.getNom();
|
||||
public final LocalDate dateFinPrevue = chantier.getDateFinPrevue();
|
||||
public final long joursRetard =
|
||||
LocalDate.now().toEpochDay()
|
||||
- chantier.getDateFinPrevue().toEpochDay();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/maintenance")
|
||||
@Operation(
|
||||
summary = "Dashboard de maintenance",
|
||||
description = "Métriques de maintenance du matériel")
|
||||
@APIResponse(responseCode = "200", description = "Métriques de maintenance récupérées")
|
||||
public Response getDashboardMaintenance() {
|
||||
logger.debug("Génération du dashboard maintenance");
|
||||
|
||||
final Object statistiquesMaintenance = maintenanceService.getStatistics();
|
||||
final List<MaintenanceMateriel> maintenancesEnRetardListe = maintenanceService.findEnRetard();
|
||||
final List<MaintenanceMateriel> prochainesMaintenancesListe =
|
||||
maintenanceService.findProchainesMaintenances(30);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final Object statistiques = statistiquesMaintenance;
|
||||
public final List<Object> maintenancesEnRetard =
|
||||
maintenancesEnRetardListe.stream()
|
||||
.map(
|
||||
maint ->
|
||||
new Object() {
|
||||
public final UUID id = maint.getId();
|
||||
public final String materiel = maint.getMateriel().getNom();
|
||||
public final String type = maint.getType().toString();
|
||||
public final LocalDate datePrevue = maint.getDatePrevue();
|
||||
public final String description = maint.getDescription();
|
||||
public final long joursRetard =
|
||||
LocalDate.now().toEpochDay()
|
||||
- maint.getDatePrevue().toEpochDay();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final List<Object> prochainesMaintenances =
|
||||
prochainesMaintenancesListe.stream()
|
||||
.map(
|
||||
maint ->
|
||||
new Object() {
|
||||
public final UUID id = maint.getId();
|
||||
public final String materiel = maint.getMateriel().getNom();
|
||||
public final String type = maint.getType().toString();
|
||||
public final LocalDate datePrevue = maint.getDatePrevue();
|
||||
public final String technicien = maint.getTechnicien();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/ressources")
|
||||
@Operation(
|
||||
summary = "Dashboard des ressources",
|
||||
description = "État des ressources humaines et matérielles")
|
||||
@APIResponse(responseCode = "200", description = "État des ressources récupéré")
|
||||
public Response getDashboardRessources() {
|
||||
logger.debug("Génération du dashboard ressources");
|
||||
|
||||
final Object statsEquipes = equipeService.getStatistics();
|
||||
final Object statsEmployes = employeService.getStatistics();
|
||||
final Object statsMateriel = materielService.getStatistics();
|
||||
|
||||
// Disponibilités actuelles
|
||||
final List<Disponibilite> disponibilitesActuelles = disponibiliteService.findActuelles();
|
||||
final List<Disponibilite> disponibilitesEnAttente = disponibiliteService.findEnAttente();
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final Object equipes = statsEquipes;
|
||||
public final Object employes = statsEmployes;
|
||||
public final Object materiel = statsMateriel;
|
||||
public final Object disponibilites =
|
||||
new Object() {
|
||||
public final long actuelles = disponibilitesActuelles.size();
|
||||
public final long enAttente = disponibilitesEnAttente.size();
|
||||
public final List<Object> enAttenteDetails =
|
||||
disponibilitesEnAttente.stream()
|
||||
.map(
|
||||
dispo ->
|
||||
new Object() {
|
||||
public final UUID id = dispo.getId();
|
||||
public final String employe =
|
||||
dispo.getEmploye().getNom()
|
||||
+ " "
|
||||
+ dispo.getEmploye().getPrenom();
|
||||
public final String type = dispo.getType().toString();
|
||||
public final LocalDateTime dateDebut = dispo.getDateDebut();
|
||||
public final LocalDateTime dateFin = dispo.getDateFin();
|
||||
public final String motif = dispo.getMotif();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/planning")
|
||||
@Operation(summary = "Dashboard du planning", description = "Vue d'ensemble du planning")
|
||||
@APIResponse(responseCode = "200", description = "Planning récupéré")
|
||||
public Response getDashboardPlanning(
|
||||
@Parameter(description = "Date de référence (yyyy-mm-dd)")
|
||||
@QueryParam("date")
|
||||
@DefaultValue("")
|
||||
String dateStr) {
|
||||
|
||||
logger.debug("Génération du dashboard planning");
|
||||
|
||||
LocalDate dateRef = dateStr.isEmpty() ? LocalDate.now() : LocalDate.parse(dateStr);
|
||||
|
||||
final Object planningWeek = planningService.getPlanningWeek(dateRef);
|
||||
final List<Object> conflits =
|
||||
planningService.detectConflicts(dateRef, dateRef.plusDays(7), null);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final LocalDate dateReference = dateRef;
|
||||
public final Object planningSemaine = planningWeek;
|
||||
public final List<Object> conflitsDetectes = conflits;
|
||||
public final boolean alerteConflits = !conflits.isEmpty();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === MÉTRIQUES TEMPS RÉEL ===
|
||||
|
||||
@GET
|
||||
@Path("/alertes")
|
||||
@Operation(
|
||||
summary = "Alertes et notifications",
|
||||
description = "Alertes nécessitant une attention immédiate")
|
||||
@APIResponse(responseCode = "200", description = "Alertes récupérées")
|
||||
public Response getAlertes() {
|
||||
logger.debug("Récupération des alertes");
|
||||
|
||||
// Alertes critiques
|
||||
final List<MaintenanceMateriel> maintenancesEnRetardAlertes = maintenanceService.findEnRetard();
|
||||
final List<Chantier> chantiersEnRetardAlertes = chantierService.findChantiersEnRetard();
|
||||
final List<Disponibilite> disponibilitesEnAttenteAlertes = disponibiliteService.findEnAttente();
|
||||
final List<Object> conflitsPlanifiesAlertes =
|
||||
planningService.detectConflicts(LocalDate.now(), LocalDate.now().plusDays(7), null);
|
||||
|
||||
final int totalAlertesCalcule =
|
||||
maintenancesEnRetardAlertes.size()
|
||||
+ chantiersEnRetardAlertes.size()
|
||||
+ disponibilitesEnAttenteAlertes.size()
|
||||
+ conflitsPlanifiesAlertes.size();
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final int totalAlertes = totalAlertesCalcule;
|
||||
public final boolean alerteCritique = totalAlertesCalcule > 0;
|
||||
|
||||
public final Object maintenance =
|
||||
new Object() {
|
||||
public final int enRetard = maintenancesEnRetardAlertes.size();
|
||||
public final List<String> details =
|
||||
maintenancesEnRetardAlertes.stream()
|
||||
.map(m -> m.getMateriel().getNom() + " - " + m.getType())
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final Object chantiers =
|
||||
new Object() {
|
||||
public final int enRetard = chantiersEnRetardAlertes.size();
|
||||
public final List<String> details =
|
||||
chantiersEnRetardAlertes.stream()
|
||||
.map(Chantier::getNom)
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final Object disponibilites =
|
||||
new Object() {
|
||||
public final int enAttente = disponibilitesEnAttenteAlertes.size();
|
||||
public final List<String> details =
|
||||
disponibilitesEnAttenteAlertes.stream()
|
||||
.map(d -> d.getEmploye().getNom() + " - " + d.getType())
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final Object planning =
|
||||
new Object() {
|
||||
public final int conflits = conflitsPlanifiesAlertes.size();
|
||||
public final boolean alerteConflits = !conflitsPlanifiesAlertes.isEmpty();
|
||||
};
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/kpi")
|
||||
@Operation(
|
||||
summary = "Indicateurs clés de performance",
|
||||
description = "KPIs principaux du système BTP")
|
||||
@APIResponse(responseCode = "200", description = "KPIs récupérés")
|
||||
public Response getKPI(
|
||||
@Parameter(description = "Période en jours", example = "30")
|
||||
@QueryParam("periode")
|
||||
@DefaultValue("30")
|
||||
int periode) {
|
||||
|
||||
logger.debug("Calcul des KPIs sur {} jours", periode);
|
||||
|
||||
final LocalDate dateDebutRef = LocalDate.now().minusDays(periode);
|
||||
final LocalDate dateFinRef = LocalDate.now();
|
||||
|
||||
// KPIs calculés
|
||||
final long chantiersTerminesCount = chantierService.findByStatut(StatutChantier.TERMINE).size();
|
||||
final long chantiersTotalCount = chantierService.count();
|
||||
final double tauxReussiteCalc =
|
||||
chantiersTotalCount > 0 ? (double) chantiersTerminesCount / chantiersTotalCount * 100 : 0;
|
||||
|
||||
final List<MaintenanceMateriel> maintenancesTermineesListe = maintenanceService.findTerminees();
|
||||
final long maintenancesTotalCount = maintenanceService.findAll().size();
|
||||
final double tauxMaintenanceRealiseeCalc =
|
||||
maintenancesTotalCount > 0
|
||||
? (double) maintenancesTermineesListe.size() / maintenancesTotalCount * 100
|
||||
: 0;
|
||||
|
||||
final long equipesTotalCount = equipeService.count();
|
||||
final long equipesOccupeesCount = equipeService.countByStatut(StatutEquipe.OCCUPEE);
|
||||
final double tauxUtilisationEquipesCalc =
|
||||
equipesTotalCount > 0 ? (double) equipesOccupeesCount / equipesTotalCount * 100 : 0;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final int periodeJours = periode;
|
||||
public final LocalDate dateDebut = dateDebutRef;
|
||||
public final LocalDate dateFin = dateFinRef;
|
||||
|
||||
public final Object chantiers =
|
||||
new Object() {
|
||||
public final double tauxReussite = Math.round(tauxReussiteCalc * 100.0) / 100.0;
|
||||
public final long termines = chantiersTerminesCount;
|
||||
public final long total = chantiersTotalCount;
|
||||
};
|
||||
|
||||
public final Object maintenance =
|
||||
new Object() {
|
||||
public final double tauxRealisation =
|
||||
Math.round(tauxMaintenanceRealiseeCalc * 100.0) / 100.0;
|
||||
public final long realisees = maintenancesTermineesListe.size();
|
||||
public final long total = maintenancesTotalCount;
|
||||
};
|
||||
|
||||
public final Object equipes =
|
||||
new Object() {
|
||||
public final double tauxUtilisation =
|
||||
Math.round(tauxUtilisationEquipesCalc * 100.0) / 100.0;
|
||||
public final long occupees = equipesOccupeesCount;
|
||||
public final long total = equipesTotalCount;
|
||||
};
|
||||
|
||||
public final LocalDateTime calculeLe = LocalDateTime.now();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === EXPORTS ET RÉSUMÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/finances")
|
||||
@Operation(
|
||||
summary = "Métriques financières",
|
||||
description = "Calculs financiers en temps réel basés sur les chantiers")
|
||||
@APIResponse(responseCode = "200", description = "Métriques financières récupérées")
|
||||
public Response getDashboardFinances(
|
||||
@Parameter(description = "Période en jours", example = "30")
|
||||
@QueryParam("periode")
|
||||
@DefaultValue("30")
|
||||
int periode) {
|
||||
|
||||
logger.debug("Calcul des métriques financières sur {} jours", periode);
|
||||
|
||||
final LocalDate dateDebutRef = LocalDate.now().minusDays(periode);
|
||||
final LocalDate dateFinRef = LocalDate.now();
|
||||
|
||||
// Récupérer tous les chantiers pour calculs financiers
|
||||
final List<Chantier> tousChantiers = chantierService.findAll();
|
||||
final List<Chantier> chantiersActifs = chantierService.findActifs();
|
||||
final List<Chantier> chantiersTermines = chantierService.findByStatut(StatutChantier.TERMINE);
|
||||
|
||||
// Calculs financiers réels
|
||||
final double budgetTotalCalcule =
|
||||
tousChantiers.stream().mapToDouble(c -> c.getMontantContrat().doubleValue()).sum();
|
||||
|
||||
final double coutReelCalcule =
|
||||
tousChantiers.stream().mapToDouble(c -> c.getCoutReel().doubleValue()).sum();
|
||||
|
||||
final double chiffreAffairesRealise =
|
||||
chantiersTermines.stream().mapToDouble(c -> c.getMontantContrat().doubleValue()).sum();
|
||||
|
||||
// Objectif CA = somme des contrats des chantiers actifs + terminés
|
||||
final double objectifCACalcule =
|
||||
chantiersActifs.stream().mapToDouble(c -> c.getMontantContrat().doubleValue()).sum()
|
||||
+ chiffreAffairesRealise;
|
||||
|
||||
final double margeGlobaleCalculee =
|
||||
chiffreAffairesRealise > 0
|
||||
? ((chiffreAffairesRealise - coutReelCalcule) / chiffreAffairesRealise * 100)
|
||||
: 0;
|
||||
|
||||
// Chantiers en retard financier (dépassement budget)
|
||||
final long chantiersEnRetardFinancier =
|
||||
tousChantiers.stream()
|
||||
.mapToLong(
|
||||
c -> c.getCoutReel().doubleValue() > c.getMontantContrat().doubleValue() ? 1 : 0)
|
||||
.sum();
|
||||
|
||||
final double tauxRentabiliteCalcule =
|
||||
budgetTotalCalcule > 0
|
||||
? ((budgetTotalCalcule - coutReelCalcule) / budgetTotalCalcule * 100)
|
||||
: 0;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final int periodeJours = periode;
|
||||
public final LocalDate dateDebut = dateDebutRef;
|
||||
public final LocalDate dateFin = dateFinRef;
|
||||
|
||||
public final Object budget =
|
||||
new Object() {
|
||||
public final double total = Math.round(budgetTotalCalcule * 100.0) / 100.0;
|
||||
public final double realise = Math.round(coutReelCalcule * 100.0) / 100.0;
|
||||
public final double reste =
|
||||
Math.round((budgetTotalCalcule - coutReelCalcule) * 100.0) / 100.0;
|
||||
public final double tauxConsommation =
|
||||
budgetTotalCalcule > 0
|
||||
? Math.round((coutReelCalcule / budgetTotalCalcule * 100) * 100.0)
|
||||
/ 100.0
|
||||
: 0;
|
||||
};
|
||||
|
||||
public final Object chiffreAffaires =
|
||||
new Object() {
|
||||
public final double realise =
|
||||
Math.round(chiffreAffairesRealise * 100.0) / 100.0;
|
||||
public final double objectif = Math.round(objectifCACalcule * 100.0) / 100.0;
|
||||
public final double tauxRealisation =
|
||||
objectifCACalcule > 0
|
||||
? Math.round((chiffreAffairesRealise / objectifCACalcule * 100) * 100.0)
|
||||
/ 100.0
|
||||
: 0;
|
||||
};
|
||||
|
||||
public final Object rentabilite =
|
||||
new Object() {
|
||||
public final double margeGlobale =
|
||||
Math.round(margeGlobaleCalculee * 100.0) / 100.0;
|
||||
public final double tauxRentabilite =
|
||||
Math.round(tauxRentabiliteCalcule * 100.0) / 100.0;
|
||||
public final long chantiersDeficitaires = chantiersEnRetardFinancier;
|
||||
public final boolean alerteRentabilite =
|
||||
tauxRentabiliteCalcule < 15.0; // Seuil d'alerte à 15%
|
||||
};
|
||||
|
||||
public final Object effectifs =
|
||||
new Object() {
|
||||
public final long totalEmployes = employeService.count();
|
||||
public final long effectifsSurSite =
|
||||
chantiersActifs.size() > 0
|
||||
? Math.round(employeService.count() * 0.8)
|
||||
: 0; // Estimation 80% sur site
|
||||
public final double coutMainOeuvre =
|
||||
Math.round(coutReelCalcule * 0.6 * 100.0)
|
||||
/ 100.0; // Estimation 60% main d'oeuvre
|
||||
};
|
||||
|
||||
public final LocalDateTime calculeLe = LocalDateTime.now();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/activites-recentes")
|
||||
@Operation(
|
||||
summary = "Activités récentes",
|
||||
description = "Liste des dernières activités du système")
|
||||
@APIResponse(responseCode = "200", description = "Activités récentes récupérées")
|
||||
public Response getActivitesRecentes(
|
||||
@Parameter(description = "Nombre d'activités à récupérer")
|
||||
@QueryParam("limit")
|
||||
@DefaultValue("10")
|
||||
int limit) {
|
||||
|
||||
logger.debug("Récupération des {} dernières activités", limit);
|
||||
|
||||
final List<Object> activites = new java.util.ArrayList<>();
|
||||
|
||||
// Chantiers récemment créés ou modifiés
|
||||
final List<Chantier> chantiersRecents = chantierService.findRecents(limit / 2);
|
||||
chantiersRecents.forEach(
|
||||
chantier -> {
|
||||
activites.add(
|
||||
new Object() {
|
||||
public final String id = chantier.getId().toString();
|
||||
public final String type = "CHANTIER";
|
||||
public final String titre = "Chantier " + chantier.getNom();
|
||||
public final String description = "Statut: " + chantier.getStatut();
|
||||
public final LocalDateTime date = chantier.getDateCreation();
|
||||
public final String utilisateur = "Système";
|
||||
public final String statut = "INFO";
|
||||
});
|
||||
});
|
||||
|
||||
// Maintenances récentes
|
||||
final List<MaintenanceMateriel> maintenancesRecentes =
|
||||
maintenanceService.findRecentes(limit / 2);
|
||||
maintenancesRecentes.forEach(
|
||||
maintenance -> {
|
||||
activites.add(
|
||||
new Object() {
|
||||
public final String id = maintenance.getId().toString();
|
||||
public final String type = "MAINTENANCE";
|
||||
public final String titre = "Maintenance " + maintenance.getMateriel().getNom();
|
||||
public final String description = maintenance.getDescription();
|
||||
public final LocalDateTime date = maintenance.getDateCreation();
|
||||
public final String utilisateur =
|
||||
maintenance.getTechnicien() != null ? maintenance.getTechnicien() : "Système";
|
||||
public final String statut =
|
||||
maintenance.getStatut().toString().equals("EN_RETARD") ? "ERROR" : "SUCCESS";
|
||||
});
|
||||
});
|
||||
|
||||
// Trier par date décroissante et limiter
|
||||
final List<Object> activitesTries =
|
||||
activites.stream()
|
||||
.sorted(
|
||||
(a, b) -> {
|
||||
try {
|
||||
LocalDateTime dateA = (LocalDateTime) a.getClass().getField("date").get(a);
|
||||
LocalDateTime dateB = (LocalDateTime) b.getClass().getField("date").get(b);
|
||||
return dateB.compareTo(dateA);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final List<Object> activites = activitesTries;
|
||||
public final int total = activitesTries.size();
|
||||
public final LocalDateTime derniereMAJ = LocalDateTime.now();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resume-quotidien")
|
||||
@Operation(summary = "Résumé quotidien", description = "Résumé de l'activité quotidienne")
|
||||
@APIResponse(responseCode = "200", description = "Résumé quotidien récupéré")
|
||||
public Response getResumeQuotidien() {
|
||||
logger.debug("Génération du résumé quotidien");
|
||||
|
||||
final LocalDate aujourdhui = LocalDate.now();
|
||||
final List<PlanningEvent> evenementsAujourdhui =
|
||||
planningService.findEventsByDateRange(aujourdhui, aujourdhui);
|
||||
final List<Disponibilite> disponibilitesActuelles = disponibiliteService.findActuelles();
|
||||
final List<MaintenanceMateriel> maintenancesDuJour =
|
||||
maintenanceService.findByDateRange(aujourdhui, aujourdhui);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final LocalDate date = aujourdhui;
|
||||
public final String jourSemaine = aujourdhui.getDayOfWeek().name();
|
||||
|
||||
public final Object planning =
|
||||
new Object() {
|
||||
public final int evenements = evenementsAujourdhui.size();
|
||||
public final List<String> resume =
|
||||
evenementsAujourdhui.stream()
|
||||
.map(PlanningEvent::getTitre)
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final Object disponibilites =
|
||||
new Object() {
|
||||
public final int actuelles = disponibilitesActuelles.size();
|
||||
public final List<String> types =
|
||||
disponibilitesActuelles.stream()
|
||||
.map(d -> d.getType().toString())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final Object maintenance =
|
||||
new Object() {
|
||||
public final int prevues = maintenancesDuJour.size();
|
||||
public final List<String> materiels =
|
||||
maintenancesDuJour.stream()
|
||||
.map(m -> m.getMateriel().getNom())
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.DevisService;
|
||||
import dev.lions.btpxpress.application.service.PdfGeneratorService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Devis;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutDevis;
|
||||
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 devis - Architecture 2025 MIGRATION: Préservation exacte de
|
||||
* toutes les API endpoints et contrats
|
||||
*/
|
||||
@Path("/api/v1/devis")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Devis", description = "Gestion des devis")
|
||||
// @Authenticated - Désactivé pour les tests
|
||||
public class DevisResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DevisResource.class);
|
||||
|
||||
@Inject DevisService devisService;
|
||||
|
||||
@Inject PdfGeneratorService pdfGeneratorService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les devis")
|
||||
@APIResponse(responseCode = "200", description = "Liste des devis récupérée avec succès")
|
||||
public Response getAllDevis(
|
||||
@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 /devis - page: {}, size: {}", page, size);
|
||||
|
||||
List<Devis> devis;
|
||||
if (page == 0 && size == 20) {
|
||||
devis = devisService.findAll();
|
||||
} else {
|
||||
devis = devisService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un devis par ID")
|
||||
@APIResponse(responseCode = "200", description = "Devis trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
public Response getDevisById(@Parameter(description = "ID du devis") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("GET /devis/{}", id);
|
||||
|
||||
Devis devis = devisService.findByIdRequired(id);
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/numero/{numero}")
|
||||
@Operation(summary = "Récupérer un devis par numéro")
|
||||
@APIResponse(responseCode = "200", description = "Devis trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
public Response getDevisByNumero(
|
||||
@Parameter(description = "Numéro du devis") @PathParam("numero") String numero) {
|
||||
|
||||
logger.debug("GET /devis/numero/{}", numero);
|
||||
|
||||
return devisService
|
||||
.findByNumero(numero)
|
||||
.map(devis -> Response.ok(devis).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/client/{clientId}")
|
||||
@Operation(summary = "Récupérer les devis d'un client")
|
||||
@APIResponse(responseCode = "200", description = "Devis du client récupérés")
|
||||
public Response getDevisByClient(
|
||||
@Parameter(description = "ID du client") @PathParam("clientId") UUID clientId) {
|
||||
|
||||
logger.debug("GET /devis/client/{}", clientId);
|
||||
|
||||
List<Devis> devis = devisService.findByClient(clientId);
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
@Operation(summary = "Récupérer les devis d'un chantier")
|
||||
@APIResponse(responseCode = "200", description = "Devis du chantier récupérés")
|
||||
public Response getDevisByChantier(
|
||||
@Parameter(description = "ID du chantier") @PathParam("chantierId") UUID chantierId) {
|
||||
|
||||
logger.debug("GET /devis/chantier/{}", chantierId);
|
||||
|
||||
List<Devis> devis = devisService.findByChantier(chantierId);
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(summary = "Récupérer les devis par statut")
|
||||
@APIResponse(responseCode = "200", description = "Devis par statut récupérés")
|
||||
public Response getDevisByStatut(
|
||||
@Parameter(description = "Statut du devis") @PathParam("statut") StatutDevis statut) {
|
||||
|
||||
logger.debug("GET /devis/statut/{}", statut);
|
||||
|
||||
List<Devis> devis = devisService.findByStatut(statut);
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-attente")
|
||||
@Operation(summary = "Récupérer les devis en attente")
|
||||
@APIResponse(responseCode = "200", description = "Devis en attente récupérés")
|
||||
public Response getDevisEnAttente() {
|
||||
logger.debug("GET /devis/en-attente");
|
||||
|
||||
List<Devis> devis = devisService.findEnAttente();
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/acceptes")
|
||||
@Operation(summary = "Récupérer les devis acceptés")
|
||||
@APIResponse(responseCode = "200", description = "Devis acceptés récupérés")
|
||||
public Response getDevisAcceptes() {
|
||||
logger.debug("GET /devis/acceptes");
|
||||
|
||||
List<Devis> devis = devisService.findAcceptes();
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/expiring")
|
||||
@Operation(summary = "Récupérer les devis expirant bientôt")
|
||||
@APIResponse(responseCode = "200", description = "Devis expirant bientôt récupérés")
|
||||
public Response getDevisExpiringBefore(
|
||||
@Parameter(description = "Date limite (format: YYYY-MM-DD)") @QueryParam("before")
|
||||
String before) {
|
||||
|
||||
logger.debug("GET /devis/expiring?before={}", before);
|
||||
|
||||
LocalDate dateLimit = before != null ? LocalDate.parse(before) : LocalDate.now().plusDays(7);
|
||||
List<Devis> devis = devisService.findExpiringBefore(dateLimit);
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Rechercher des devis")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
public Response searchDevis(
|
||||
@Parameter(description = "Date de début d'émission (format: YYYY-MM-DD)")
|
||||
@QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin d'émission (format: YYYY-MM-DD)") @QueryParam("dateFin")
|
||||
String dateFin) {
|
||||
|
||||
logger.debug("GET /devis/search - dateDebut: {}, dateFin: {}", dateDebut, dateFin);
|
||||
|
||||
List<Devis> devis;
|
||||
|
||||
if (dateDebut != null && dateFin != null) {
|
||||
LocalDate debut = LocalDate.parse(dateDebut);
|
||||
LocalDate fin = LocalDate.parse(dateFin);
|
||||
devis = devisService.findByDateEmission(debut, fin);
|
||||
} else {
|
||||
devis = devisService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(devis).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'ÉCRITURE - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer un nouveau devis")
|
||||
@APIResponse(responseCode = "201", description = "Devis créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createDevis(@Valid @NotNull Devis devis) {
|
||||
logger.debug("POST /devis");
|
||||
|
||||
Devis createdDevis = devisService.create(devis);
|
||||
return Response.status(Response.Status.CREATED).entity(createdDevis).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un devis")
|
||||
@APIResponse(responseCode = "200", description = "Devis mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateDevis(
|
||||
@Parameter(description = "ID du devis") @PathParam("id") UUID id,
|
||||
@Valid @NotNull Devis devis) {
|
||||
|
||||
logger.debug("PUT /devis/{}", id);
|
||||
|
||||
Devis updatedDevis = devisService.update(id, devis);
|
||||
return Response.ok(updatedDevis).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/statut")
|
||||
@Operation(summary = "Mettre à jour le statut d'un devis")
|
||||
@APIResponse(responseCode = "200", description = "Statut mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Transition de statut invalide")
|
||||
public Response updateDevisStatut(
|
||||
@Parameter(description = "ID du devis") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Nouveau statut") @QueryParam("statut") @NotNull
|
||||
StatutDevis statut) {
|
||||
|
||||
logger.debug("PUT /devis/{}/statut - nouveau statut: {}", id, statut);
|
||||
|
||||
Devis updatedDevis = devisService.updateStatut(id, statut);
|
||||
return Response.ok(updatedDevis).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/envoyer")
|
||||
@Operation(summary = "Envoyer un devis")
|
||||
@APIResponse(responseCode = "200", description = "Devis envoyé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Devis ne peut pas être envoyé")
|
||||
public Response envoyerDevis(@Parameter(description = "ID du devis") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("PUT /devis/{}/envoyer", id);
|
||||
|
||||
Devis devisEnvoye = devisService.envoyer(id);
|
||||
return Response.ok(devisEnvoye).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer un devis")
|
||||
@APIResponse(responseCode = "204", description = "Devis supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Devis ne peut pas être supprimé")
|
||||
public Response deleteDevis(@Parameter(description = "ID du devis") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("DELETE /devis/{}", id);
|
||||
|
||||
devisService.delete(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre de devis")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de devis")
|
||||
public Response countDevis() {
|
||||
logger.debug("GET /devis/count");
|
||||
|
||||
long count = devisService.count();
|
||||
return Response.ok(count).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count/statut/{statut}")
|
||||
@Operation(summary = "Compter le nombre de devis par statut")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de devis par statut")
|
||||
public Response countDevisByStatut(
|
||||
@Parameter(description = "Statut du devis") @PathParam("statut") StatutDevis statut) {
|
||||
|
||||
logger.debug("GET /devis/count/statut/{}", statut);
|
||||
|
||||
long count = devisService.countByStatut(statut);
|
||||
return Response.ok(count).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS PDF - GÉNÉRATION DE DOCUMENTS ===
|
||||
|
||||
@GET
|
||||
@Path("/{id}/pdf")
|
||||
@Operation(summary = "Générer le PDF d'un devis")
|
||||
@APIResponse(responseCode = "200", description = "PDF généré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
public Response generateDevisPdf(
|
||||
@Parameter(description = "ID du devis") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("GET /devis/{}/pdf", id);
|
||||
|
||||
Devis devis = devisService.findByIdRequired(id);
|
||||
byte[] pdfContent = pdfGeneratorService.generateDevisPdf(devis);
|
||||
String fileName = pdfGeneratorService.generateFileName("devis", devis.getNumero());
|
||||
|
||||
return Response.ok(pdfContent)
|
||||
.header("Content-Type", "application/pdf")
|
||||
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.DisponibiliteService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Disponibilite;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeDisponibilite;
|
||||
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.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
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 disponibilités - Architecture 2025 RH: API complète de gestion
|
||||
* des disponibilités employés
|
||||
*/
|
||||
@Path("/api/v1/disponibilites")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Disponibilités", description = "Gestion des disponibilités et absences des employés")
|
||||
public class DisponibiliteResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DisponibiliteResource.class);
|
||||
|
||||
@Inject DisponibiliteService disponibiliteService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les disponibilités",
|
||||
description =
|
||||
"Récupère la liste paginée de toutes les disponibilités avec filtres optionnels")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des disponibilités récupérée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getAllDisponibilites(
|
||||
@Parameter(description = "Numéro de page (0-indexé)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Filtrer par employé (UUID)") @QueryParam("employeId")
|
||||
UUID employeId,
|
||||
@Parameter(description = "Filtrer par type de disponibilité") @QueryParam("type") String type,
|
||||
@Parameter(description = "Filtrer par statut d'approbation") @QueryParam("approuvee")
|
||||
Boolean approuvee) {
|
||||
|
||||
logger.debug("Récupération des disponibilités - page: {}, taille: {}", page, size);
|
||||
|
||||
List<Disponibilite> disponibilites;
|
||||
|
||||
if (employeId != null) {
|
||||
disponibilites = disponibiliteService.findByEmployeId(employeId);
|
||||
} else if (type != null) {
|
||||
TypeDisponibilite typeEnum = TypeDisponibilite.valueOf(type.toUpperCase());
|
||||
disponibilites = disponibiliteService.findByType(typeEnum);
|
||||
} else if (approuvee != null && !approuvee) {
|
||||
disponibilites = disponibiliteService.findEnAttente();
|
||||
} else if (approuvee != null && approuvee) {
|
||||
disponibilites = disponibiliteService.findApprouvees();
|
||||
} else {
|
||||
disponibilites = disponibiliteService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(disponibilites).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une disponibilité par ID",
|
||||
description = "Récupère les détails d'une disponibilité spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilité trouvée",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
@APIResponse(responseCode = "404", description = "Disponibilité non trouvée")
|
||||
public Response getDisponibiliteById(
|
||||
@Parameter(description = "Identifiant unique de la disponibilité", required = true)
|
||||
@PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération de la disponibilité avec l'ID: {}", id);
|
||||
|
||||
return disponibiliteService
|
||||
.findById(id)
|
||||
.map(disponibilite -> Response.ok(disponibilite).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/actuelles")
|
||||
@Operation(
|
||||
summary = "Lister les disponibilités actuelles",
|
||||
description = "Récupère toutes les disponibilités actuellement actives")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilités actuelles récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getDisponibilitesActuelles() {
|
||||
logger.debug("Récupération des disponibilités actuelles");
|
||||
List<Disponibilite> disponibilites = disponibiliteService.findActuelles();
|
||||
return Response.ok(disponibilites).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/futures")
|
||||
@Operation(
|
||||
summary = "Lister les disponibilités futures",
|
||||
description = "Récupère toutes les disponibilités programmées pour le futur")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilités futures récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getDisponibilitesFutures() {
|
||||
logger.debug("Récupération des disponibilités futures");
|
||||
List<Disponibilite> disponibilites = disponibiliteService.findFutures();
|
||||
return Response.ok(disponibilites).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-attente")
|
||||
@Operation(
|
||||
summary = "Lister les demandes en attente",
|
||||
description = "Récupère toutes les demandes de disponibilité en attente d'approbation")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Demandes en attente récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getDemandesEnAttente() {
|
||||
logger.debug("Récupération des demandes en attente");
|
||||
List<Disponibilite> disponibilites = disponibiliteService.findEnAttente();
|
||||
return Response.ok(disponibilites).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/periode")
|
||||
@Operation(
|
||||
summary = "Lister les disponibilités pour une période",
|
||||
description = "Récupère toutes les disponibilités dans une période donnée")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilités de la période récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getDisponibilitesPourPeriode(
|
||||
@Parameter(description = "Date de début (yyyy-mm-dd)", required = true)
|
||||
@QueryParam("dateDebut")
|
||||
@NotNull
|
||||
LocalDate dateDebut,
|
||||
@Parameter(description = "Date de fin (yyyy-mm-dd)", required = true)
|
||||
@QueryParam("dateFin")
|
||||
@NotNull
|
||||
LocalDate dateFin) {
|
||||
|
||||
logger.debug("Récupération des disponibilités pour la période {} - {}", dateDebut, dateFin);
|
||||
List<Disponibilite> disponibilites = disponibiliteService.findPourPeriode(dateDebut, dateFin);
|
||||
return Response.ok(disponibilites).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION CRUD ===
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle disponibilité",
|
||||
description = "Créé une nouvelle demande de disponibilité pour un employé")
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Disponibilité créée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides ou conflit détecté")
|
||||
public Response createDisponibilite(@Valid @NotNull CreateDisponibiliteRequest request) {
|
||||
|
||||
logger.info("Création d'une nouvelle disponibilité pour l'employé: {}", request.employeId);
|
||||
|
||||
Disponibilite disponibilite =
|
||||
disponibiliteService.createDisponibilite(
|
||||
request.employeId, request.dateDebut, request.dateFin, request.type, request.motif);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(disponibilite).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour une disponibilité",
|
||||
description = "Met à jour les informations d'une disponibilité existante")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilité mise à jour avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
@APIResponse(responseCode = "404", description = "Disponibilité non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateDisponibilite(
|
||||
@Parameter(description = "Identifiant de la disponibilité", required = true) @PathParam("id")
|
||||
UUID id,
|
||||
@Valid @NotNull UpdateDisponibiliteRequest request) {
|
||||
|
||||
logger.info("Mise à jour de la disponibilité: {}", id);
|
||||
|
||||
Disponibilite disponibilite =
|
||||
disponibiliteService.updateDisponibilite(
|
||||
id, request.dateDebut, request.dateFin, request.motif);
|
||||
|
||||
return Response.ok(disponibilite).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/approuver")
|
||||
@Operation(
|
||||
summary = "Approuver une demande de disponibilité",
|
||||
description = "Approuve une demande de disponibilité en attente")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilité approuvée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
@APIResponse(responseCode = "404", description = "Disponibilité non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Disponibilité déjà approuvée")
|
||||
public Response approuverDisponibilite(
|
||||
@Parameter(description = "Identifiant de la disponibilité", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Approbation de la disponibilité: {}", id);
|
||||
|
||||
Disponibilite disponibilite = disponibiliteService.approuverDisponibilite(id);
|
||||
return Response.ok(disponibilite).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/rejeter")
|
||||
@Operation(
|
||||
summary = "Rejeter une demande de disponibilité",
|
||||
description = "Rejette une demande de disponibilité avec une raison")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Disponibilité rejetée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
@APIResponse(responseCode = "404", description = "Disponibilité non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Impossible de rejeter")
|
||||
public Response rejeterDisponibilite(
|
||||
@Parameter(description = "Identifiant de la disponibilité", required = true) @PathParam("id")
|
||||
UUID id,
|
||||
@Valid @NotNull RejetDisponibiliteRequest request) {
|
||||
|
||||
logger.info("Rejet de la disponibilité: {}", id);
|
||||
|
||||
Disponibilite disponibilite =
|
||||
disponibiliteService.rejeterDisponibilite(id, request.raisonRejet);
|
||||
return Response.ok(disponibilite).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une disponibilité",
|
||||
description = "Supprime définitivement une disponibilité")
|
||||
@APIResponse(responseCode = "204", description = "Disponibilité supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Disponibilité non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Impossible de supprimer")
|
||||
public Response deleteDisponibilite(
|
||||
@Parameter(description = "Identifiant de la disponibilité", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Suppression de la disponibilité: {}", id);
|
||||
|
||||
disponibiliteService.deleteDisponibilite(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE VALIDATION ===
|
||||
|
||||
@GET
|
||||
@Path("/employe/{employeId}/disponible")
|
||||
@Operation(
|
||||
summary = "Vérifier la disponibilité d'un employé",
|
||||
description = "Vérifie si un employé est disponible pour une période donnée")
|
||||
@APIResponse(responseCode = "200", description = "Statut de disponibilité retourné")
|
||||
public Response checkEmployeDisponibilite(
|
||||
@Parameter(description = "Identifiant de l'employé", required = true) @PathParam("employeId")
|
||||
UUID employeId,
|
||||
@Parameter(description = "Date/heure de début", required = true)
|
||||
@QueryParam("dateDebut")
|
||||
@NotNull
|
||||
LocalDateTime dateDebut,
|
||||
@Parameter(description = "Date/heure de fin", required = true) @QueryParam("dateFin") @NotNull
|
||||
LocalDateTime dateFin) {
|
||||
|
||||
logger.debug("Vérification de disponibilité pour l'employé {}", employeId);
|
||||
|
||||
boolean estDisponible = disponibiliteService.isEmployeDisponible(employeId, dateDebut, dateFin);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final boolean disponible = estDisponible;
|
||||
public final String message =
|
||||
estDisponible
|
||||
? "Employé disponible pour cette période"
|
||||
: "Employé indisponible pour cette période";
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/employe/{employeId}/conflits")
|
||||
@Operation(
|
||||
summary = "Rechercher les conflits de disponibilité",
|
||||
description = "Trouve les conflits de disponibilité pour un employé et une période")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Conflits trouvés",
|
||||
content = @Content(schema = @Schema(implementation = Disponibilite.class)))
|
||||
public Response getConflits(
|
||||
@Parameter(description = "Identifiant de l'employé", required = true) @PathParam("employeId")
|
||||
UUID employeId,
|
||||
@Parameter(description = "Date/heure de début", required = true)
|
||||
@QueryParam("dateDebut")
|
||||
@NotNull
|
||||
LocalDateTime dateDebut,
|
||||
@Parameter(description = "Date/heure de fin", required = true) @QueryParam("dateFin") @NotNull
|
||||
LocalDateTime dateFin,
|
||||
@Parameter(description = "ID à exclure de la recherche") @QueryParam("excludeId")
|
||||
UUID excludeId) {
|
||||
|
||||
logger.debug("Recherche de conflits pour l'employé {}", employeId);
|
||||
|
||||
List<Disponibilite> conflits =
|
||||
disponibiliteService.getConflicts(employeId, dateDebut, dateFin, excludeId);
|
||||
|
||||
return Response.ok(conflits).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Obtenir les statistiques des disponibilités",
|
||||
description = "Récupère les statistiques globales des disponibilités")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
logger.debug("Récupération des statistiques des disponibilités");
|
||||
Object statistiques = disponibiliteService.getStatistics();
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-type")
|
||||
@Operation(
|
||||
summary = "Statistiques par type de disponibilité",
|
||||
description = "Récupère les statistiques détaillées par type")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par type récupérées")
|
||||
public Response getStatistiquesParType() {
|
||||
logger.debug("Récupération des statistiques par type");
|
||||
List<Object[]> stats = disponibiliteService.getStatsByType();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-employe")
|
||||
@Operation(
|
||||
summary = "Statistiques par employé",
|
||||
description = "Récupère les statistiques de disponibilité par employé")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par employé récupérées")
|
||||
public Response getStatistiquesParEmploye() {
|
||||
logger.debug("Récupération des statistiques par employé");
|
||||
List<Object[]> stats = disponibiliteService.getStatsByEmployee();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreateDisponibiliteRequest {
|
||||
@Schema(description = "Identifiant unique de l'employé", required = true)
|
||||
public UUID employeId;
|
||||
|
||||
@Schema(
|
||||
description = "Date et heure de début de la disponibilité",
|
||||
required = true,
|
||||
example = "2024-03-15T08:00:00")
|
||||
public LocalDateTime dateDebut;
|
||||
|
||||
@Schema(
|
||||
description = "Date et heure de fin de la disponibilité",
|
||||
required = true,
|
||||
example = "2024-03-20T18:00:00")
|
||||
public LocalDateTime dateFin;
|
||||
|
||||
@Schema(
|
||||
description = "Type de disponibilité",
|
||||
required = true,
|
||||
enumeration = {
|
||||
"CONGE_PAYE",
|
||||
"CONGE_SANS_SOLDE",
|
||||
"ARRET_MALADIE",
|
||||
"FORMATION",
|
||||
"ABSENCE",
|
||||
"HORAIRE_REDUIT"
|
||||
})
|
||||
public String type;
|
||||
|
||||
@Schema(description = "Motif ou raison de la disponibilité", example = "Congés annuels")
|
||||
public String motif;
|
||||
}
|
||||
|
||||
public static class UpdateDisponibiliteRequest {
|
||||
@Schema(description = "Nouvelle date de début")
|
||||
public LocalDateTime dateDebut;
|
||||
|
||||
@Schema(description = "Nouvelle date de fin")
|
||||
public LocalDateTime dateFin;
|
||||
|
||||
@Schema(description = "Nouveau motif")
|
||||
public String motif;
|
||||
}
|
||||
|
||||
public static class RejetDisponibiliteRequest {
|
||||
@Schema(description = "Raison du rejet de la demande", required = true)
|
||||
public String raisonRejet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.DocumentService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Document;
|
||||
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 jakarta.ws.rs.core.StreamingOutput;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
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.jboss.resteasy.reactive.PartType;
|
||||
import org.jboss.resteasy.reactive.RestForm;
|
||||
import org.jboss.resteasy.reactive.multipart.FileUpload;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des documents - Architecture 2025 DOCUMENTS: API complète de
|
||||
* gestion documentaire avec upload
|
||||
*/
|
||||
@Path("/api/v1/documents")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Documents", description = "Gestion des documents et fichiers BTP")
|
||||
public class DocumentResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DocumentResource.class);
|
||||
|
||||
@Inject DocumentService documentService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister tous les documents",
|
||||
description = "Récupère la liste paginée de tous les documents avec filtres optionnels")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des documents récupérée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getAllDocuments(
|
||||
@Parameter(description = "Numéro de page (0-indexé)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Filtrer par type de document") @QueryParam("type") String type,
|
||||
@Parameter(description = "Filtrer par chantier (UUID)") @QueryParam("chantierId")
|
||||
UUID chantierId,
|
||||
@Parameter(description = "Filtrer par matériel (UUID)") @QueryParam("materielId")
|
||||
UUID materielId,
|
||||
@Parameter(description = "Filtrer par client (UUID)") @QueryParam("clientId") UUID clientId,
|
||||
@Parameter(description = "Filtrer par employé (UUID)") @QueryParam("employeId")
|
||||
UUID employeId,
|
||||
@Parameter(description = "Afficher seulement les documents publics") @QueryParam("public")
|
||||
Boolean estPublic,
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search) {
|
||||
|
||||
logger.debug("Récupération des documents - page: {}, taille: {}", page, size);
|
||||
|
||||
List<Document> documents;
|
||||
|
||||
if (search != null || type != null || chantierId != null || materielId != null) {
|
||||
documents = documentService.search(search, type, chantierId, materielId, estPublic);
|
||||
} else if (chantierId != null) {
|
||||
documents = documentService.findByChantier(chantierId);
|
||||
} else if (materielId != null) {
|
||||
documents = documentService.findByMateriel(materielId);
|
||||
} else if (clientId != null) {
|
||||
documents = documentService.findByClient(clientId);
|
||||
} else if (employeId != null) {
|
||||
documents = documentService.findByEmploye(employeId);
|
||||
} else if (estPublic != null && estPublic) {
|
||||
documents = documentService.findPublics();
|
||||
} else {
|
||||
documents = documentService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(documents).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer un document par ID",
|
||||
description = "Récupère les métadonnées d'un document spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Document trouvé",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
@APIResponse(responseCode = "404", description = "Document non trouvé")
|
||||
public Response getDocumentById(
|
||||
@Parameter(description = "Identifiant unique du document", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération du document avec l'ID: {}", id);
|
||||
|
||||
return documentService
|
||||
.findById(id)
|
||||
.map(document -> Response.ok(document).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/images")
|
||||
@Operation(
|
||||
summary = "Lister les documents images",
|
||||
description = "Récupère tous les documents de type image")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Images récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getImages() {
|
||||
logger.debug("Récupération des documents images");
|
||||
List<Document> images = documentService.findImages();
|
||||
return Response.ok(images).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/pdfs")
|
||||
@Operation(summary = "Lister les documents PDF", description = "Récupère tous les documents PDF")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "PDFs récupérés",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getPdfs() {
|
||||
logger.debug("Récupération des documents PDF");
|
||||
List<Document> pdfs = documentService.findPdfs();
|
||||
return Response.ok(pdfs).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/publics")
|
||||
@Operation(
|
||||
summary = "Lister les documents publics",
|
||||
description = "Récupère tous les documents marqués comme publics")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Documents publics récupérés",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getDocumentsPublics() {
|
||||
logger.debug("Récupération des documents publics");
|
||||
List<Document> documents = documentService.findPublics();
|
||||
return Response.ok(documents).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recents")
|
||||
@Operation(
|
||||
summary = "Lister les documents récents",
|
||||
description = "Récupère les documents les plus récemment ajoutés")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Documents récents récupérés",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getDocumentsRecents(
|
||||
@Parameter(description = "Nombre de documents à retourner", example = "10")
|
||||
@QueryParam("limite")
|
||||
@DefaultValue("10")
|
||||
int limite) {
|
||||
|
||||
logger.debug("Récupération des {} documents les plus récents", limite);
|
||||
List<Document> documents = documentService.findRecents(limite);
|
||||
return Response.ok(documents).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/orphelins")
|
||||
@Operation(
|
||||
summary = "Lister les documents orphelins",
|
||||
description = "Récupère les documents non liés à une entité spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Documents orphelins récupérés",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getDocumentsOrphelins() {
|
||||
logger.debug("Récupération des documents orphelins");
|
||||
List<Document> documents = documentService.findDocumentsOrphelins();
|
||||
return Response.ok(documents).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'UPLOAD ===
|
||||
|
||||
@POST
|
||||
@Path("/upload")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(
|
||||
summary = "Uploader un nouveau document",
|
||||
description = "Upload un fichier avec ses métadonnées")
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Document uploadé avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides ou fichier non supporté")
|
||||
public Response uploadDocument(
|
||||
@RestForm("nom") String nom,
|
||||
@RestForm("description") String description,
|
||||
@RestForm("type") String type,
|
||||
@RestForm("file") FileUpload file,
|
||||
@RestForm("fileName") String fileName,
|
||||
@RestForm("contentType") String contentType,
|
||||
@RestForm("fileSize") Long fileSize,
|
||||
@RestForm("chantierId") UUID chantierId,
|
||||
@RestForm("materielId") UUID materielId,
|
||||
@RestForm("equipeId") UUID equipeId,
|
||||
@RestForm("employeId") UUID employeId) {
|
||||
|
||||
logger.info("Upload de document: {}", nom);
|
||||
|
||||
Document document =
|
||||
documentService.uploadDocument(
|
||||
nom,
|
||||
description,
|
||||
type,
|
||||
file,
|
||||
fileName,
|
||||
contentType,
|
||||
fileSize != null ? fileSize : 0L,
|
||||
chantierId,
|
||||
materielId,
|
||||
equipeId,
|
||||
employeId,
|
||||
null, // clientId - ajouté si besoin
|
||||
null, // tags - ajouté si besoin
|
||||
false, // estPublic - défaut
|
||||
null); // userId - ajouté si besoin
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(document).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE TÉLÉCHARGEMENT ===
|
||||
|
||||
@GET
|
||||
@Path("/{id}/download")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Operation(
|
||||
summary = "Télécharger un document",
|
||||
description = "Télécharge le fichier physique d'un document")
|
||||
@APIResponse(responseCode = "200", description = "Fichier téléchargé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Document ou fichier non trouvé")
|
||||
public Response downloadDocument(
|
||||
@Parameter(description = "Identifiant du document", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Téléchargement du document: {}", id);
|
||||
|
||||
Document document = documentService.findByIdRequired(id);
|
||||
InputStream inputStream = documentService.downloadDocument(id);
|
||||
|
||||
StreamingOutput streamingOutput =
|
||||
output -> {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
inputStream.close();
|
||||
};
|
||||
|
||||
return Response.ok(streamingOutput)
|
||||
.header("Content-Disposition", "attachment; filename=\"" + document.getNomFichier() + "\"")
|
||||
.header("Content-Type", document.getTypeMime())
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/preview")
|
||||
@Operation(
|
||||
summary = "Prévisualiser un document",
|
||||
description = "Affiche le document dans le navigateur (pour images et PDFs)")
|
||||
@APIResponse(responseCode = "200", description = "Prévisualisation disponible")
|
||||
@APIResponse(responseCode = "404", description = "Document non trouvé")
|
||||
public Response previewDocument(
|
||||
@Parameter(description = "Identifiant du document", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Prévisualisation du document: {}", id);
|
||||
|
||||
Document document = documentService.findByIdRequired(id);
|
||||
|
||||
// Vérifier si le document peut être prévisualisé
|
||||
if (!document.isImage() && !document.isPdf()) {
|
||||
return Response.status(Response.Status.NOT_ACCEPTABLE)
|
||||
.entity("Ce type de document ne peut pas être prévisualisé")
|
||||
.build();
|
||||
}
|
||||
|
||||
InputStream inputStream = documentService.downloadDocument(id);
|
||||
|
||||
StreamingOutput streamingOutput =
|
||||
output -> {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
inputStream.close();
|
||||
};
|
||||
|
||||
return Response.ok(streamingOutput)
|
||||
.header("Content-Type", document.getTypeMime())
|
||||
.header("Content-Disposition", "inline; filename=\"" + document.getNomFichier() + "\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour un document",
|
||||
description = "Met à jour les métadonnées d'un document")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Document mis à jour avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
@APIResponse(responseCode = "404", description = "Document non trouvé")
|
||||
public Response updateDocument(
|
||||
@Parameter(description = "Identifiant du document", required = true) @PathParam("id") UUID id,
|
||||
@Valid @NotNull UpdateDocumentRequest request) {
|
||||
|
||||
logger.info("Mise à jour du document: {}", id);
|
||||
|
||||
Document document =
|
||||
documentService.updateDocument(
|
||||
id, request.nom, request.description, request.tags, request.estPublic);
|
||||
|
||||
return Response.ok(document).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer un document",
|
||||
description = "Supprime définitivement un document et son fichier")
|
||||
@APIResponse(responseCode = "204", description = "Document supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Document non trouvé")
|
||||
public Response deleteDocument(
|
||||
@Parameter(description = "Identifiant du document", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Suppression du document: {}", id);
|
||||
|
||||
documentService.deleteDocument(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Obtenir les statistiques des documents",
|
||||
description = "Récupère les statistiques globales des documents")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
logger.debug("Récupération des statistiques des documents");
|
||||
Object statistiques = documentService.getStatistics();
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-type")
|
||||
@Operation(
|
||||
summary = "Statistiques par type de document",
|
||||
description = "Récupère les statistiques détaillées par type")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par type récupérées")
|
||||
public Response getStatistiquesParType() {
|
||||
logger.debug("Récupération des statistiques par type");
|
||||
List<Object> stats = documentService.getStatsByType();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-extension")
|
||||
@Operation(
|
||||
summary = "Statistiques par extension de fichier",
|
||||
description = "Récupère les statistiques par extension de fichier")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par extension récupérées")
|
||||
public Response getStatistiquesParExtension() {
|
||||
logger.debug("Récupération des statistiques par extension");
|
||||
List<Object> stats = documentService.getStatsByExtension();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/tendances-upload")
|
||||
@Operation(
|
||||
summary = "Tendances des uploads",
|
||||
description = "Récupère les tendances d'upload sur plusieurs mois")
|
||||
@APIResponse(responseCode = "200", description = "Tendances d'upload récupérées")
|
||||
public Response getTendancesUpload(
|
||||
@Parameter(description = "Nombre de mois", example = "12")
|
||||
@QueryParam("mois")
|
||||
@DefaultValue("12")
|
||||
int mois) {
|
||||
|
||||
logger.debug("Récupération des tendances d'upload sur {} mois", mois);
|
||||
List<Object> tendances = documentService.getUploadTrends(mois);
|
||||
return Response.ok(tendances).build();
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class UploadDocumentForm {
|
||||
@RestForm("nom")
|
||||
@Schema(description = "Nom du document", required = true)
|
||||
public String nom;
|
||||
|
||||
@RestForm("description")
|
||||
@Schema(description = "Description du document")
|
||||
public String description;
|
||||
|
||||
@RestForm("type")
|
||||
@Schema(
|
||||
description = "Type de document",
|
||||
required = true,
|
||||
enumeration = {
|
||||
"PLAN",
|
||||
"PERMIS_CONSTRUIRE",
|
||||
"PHOTO_CHANTIER",
|
||||
"CONTRAT",
|
||||
"FACTURE",
|
||||
"AUTRE"
|
||||
})
|
||||
public String type;
|
||||
|
||||
@RestForm("file")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Schema(description = "Fichier à uploader", required = true)
|
||||
public InputStream file;
|
||||
|
||||
@RestForm("fileName")
|
||||
@Schema(description = "Nom du fichier", required = true)
|
||||
public String fileName;
|
||||
|
||||
@RestForm("contentType")
|
||||
@Schema(description = "Type MIME du fichier")
|
||||
public String contentType;
|
||||
|
||||
@RestForm("fileSize")
|
||||
@Schema(description = "Taille du fichier en bytes")
|
||||
public long fileSize;
|
||||
|
||||
@RestForm("chantierId")
|
||||
@Schema(description = "ID du chantier associé")
|
||||
public UUID chantierId;
|
||||
|
||||
@RestForm("materielId")
|
||||
@Schema(description = "ID du matériel associé")
|
||||
public UUID materielId;
|
||||
|
||||
@RestForm("employeId")
|
||||
@Schema(description = "ID de l'employé associé")
|
||||
public UUID employeId;
|
||||
|
||||
@RestForm("clientId")
|
||||
@Schema(description = "ID du client associé")
|
||||
public UUID clientId;
|
||||
|
||||
@RestForm("tags")
|
||||
@Schema(description = "Tags séparés par des virgules")
|
||||
public String tags;
|
||||
|
||||
@RestForm("estPublic")
|
||||
@Schema(description = "Document public ou privé")
|
||||
public Boolean estPublic;
|
||||
|
||||
@RestForm("userId")
|
||||
@Schema(description = "ID de l'utilisateur qui upload")
|
||||
public UUID userId;
|
||||
}
|
||||
|
||||
public static class UpdateDocumentRequest {
|
||||
@Schema(description = "Nouveau nom du document")
|
||||
public String nom;
|
||||
|
||||
@Schema(description = "Nouvelle description")
|
||||
public String description;
|
||||
|
||||
@Schema(description = "Nouveaux tags")
|
||||
public String tags;
|
||||
|
||||
@Schema(description = "Visibilité publique")
|
||||
public Boolean estPublic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.EmployeService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Employe;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutEmploye;
|
||||
import jakarta.inject.Inject;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des employés - Architecture 2025 MIGRATION: Préservation exacte de
|
||||
* tous les endpoints critiques
|
||||
*/
|
||||
@Path("/api/v1/employes")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Employés", description = "Gestion des employés")
|
||||
public class EmployeResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EmployeResource.class);
|
||||
|
||||
@Inject EmployeService employeService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les employés")
|
||||
@APIResponse(responseCode = "200", description = "Liste des employés récupérée avec succès")
|
||||
public Response getAllEmployes(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search,
|
||||
@Parameter(description = "Statut de l'employé") @QueryParam("statut") String statut) {
|
||||
try {
|
||||
List<Employe> employes;
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
employes = employeService.findByStatut(StatutEmploye.valueOf(statut.toUpperCase()));
|
||||
} else if (search != null && !search.isEmpty()) {
|
||||
employes = employeService.search(search, null, null, null);
|
||||
} else {
|
||||
employes = employeService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(employes).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des employés", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des employés: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un employé par ID")
|
||||
@APIResponse(responseCode = "200", description = "Employé récupéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Employé non trouvé")
|
||||
public Response getEmployeById(
|
||||
@Parameter(description = "ID de l'employé") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID employeId = UUID.fromString(id);
|
||||
return employeService
|
||||
.findById(employeId)
|
||||
.map(employe -> Response.ok(employe).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Employé non trouvé avec l'ID: " + id)
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID d'employé invalide: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de l'employé {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de l'employé: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre d'employés")
|
||||
@APIResponse(responseCode = "200", description = "Nombre d'employés retourné avec succès")
|
||||
public Response countEmployes() {
|
||||
try {
|
||||
long count = employeService.count();
|
||||
return Response.ok(new CountResponse(count)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du comptage des employés", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du comptage des employés: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques des employés")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStats() {
|
||||
try {
|
||||
Object stats = employeService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques des employés", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/disponibles")
|
||||
@Operation(summary = "Récupérer les employés disponibles")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des employés disponibles récupérée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de date manquants")
|
||||
public Response getEmployesDisponibles(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @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();
|
||||
}
|
||||
List<Employe> employes = employeService.findDisponibles(dateDebut, dateFin);
|
||||
return Response.ok(employes).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des employés disponibles", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des employés disponibles: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/actifs")
|
||||
@Operation(summary = "Récupérer les employés actifs")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des employés actifs récupérée avec succès")
|
||||
public Response getEmployesActifs() {
|
||||
try {
|
||||
List<Employe> employes = employeService.findByStatut(StatutEmploye.ACTIF);
|
||||
return Response.ok(employes).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des employés actifs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des employés actifs: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CLASSES UTILITAIRES
|
||||
// ============================================
|
||||
|
||||
public static record CountResponse(long count) {}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.EquipeService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Equipe;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutEquipe;
|
||||
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 équipes - Architecture 2025 MÉTIER: Gestion complète des
|
||||
* équipes BTP avec membres et disponibilités
|
||||
*/
|
||||
@Path("/api/v1/equipes")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Équipes", description = "Gestion des équipes de travail BTP")
|
||||
public class EquipeResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EquipeResource.class);
|
||||
|
||||
@Inject EquipeService equipeService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer toutes les équipes")
|
||||
@APIResponse(responseCode = "200", description = "Liste des équipes récupérée avec succès")
|
||||
public Response getAllEquipes(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search,
|
||||
@Parameter(description = "Filtrer par statut") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Filtrer par spécialité") @QueryParam("specialite")
|
||||
String specialite,
|
||||
@Parameter(description = "Nombre minimum de membres") @QueryParam("minMembers")
|
||||
Integer minMembers,
|
||||
@Parameter(description = "Nombre maximum de membres") @QueryParam("maxMembers")
|
||||
Integer maxMembers,
|
||||
@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) {
|
||||
try {
|
||||
List<Equipe> equipes;
|
||||
|
||||
if (search != null && !search.isEmpty()) {
|
||||
equipes = equipeService.search(search);
|
||||
} else if (statut != null || specialite != null || minMembers != null || maxMembers != null) {
|
||||
StatutEquipe statutEquipe =
|
||||
statut != null ? StatutEquipe.valueOf(statut.toUpperCase()) : null;
|
||||
equipes =
|
||||
equipeService.findByMultipleCriteria(statutEquipe, specialite, minMembers, maxMembers);
|
||||
} else {
|
||||
equipes = equipeService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(equipes).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Paramètres invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des équipes", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des équipes: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer une équipe par ID")
|
||||
@APIResponse(responseCode = "200", description = "Équipe récupérée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe non trouvée")
|
||||
public Response getEquipeById(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
return equipeService
|
||||
.findById(equipeId)
|
||||
.map(equipe -> Response.ok(equipe).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Équipe non trouvée avec l'ID: " + id)
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID d'équipe invalide: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de l'équipe: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre d'équipes")
|
||||
@APIResponse(responseCode = "200", description = "Nombre d'équipes retourné avec succès")
|
||||
public Response countEquipes(
|
||||
@Parameter(description = "Filtrer par statut") @QueryParam("statut") String statut) {
|
||||
try {
|
||||
long count;
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
StatutEquipe statutEquipe = StatutEquipe.valueOf(statut.toUpperCase());
|
||||
count = equipeService.countByStatut(statutEquipe);
|
||||
} else {
|
||||
count = equipeService.count();
|
||||
}
|
||||
|
||||
return Response.ok(new CountResponse(count)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Statut invalide: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du comptage des équipes", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du comptage des équipes: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques des équipes")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStats() {
|
||||
try {
|
||||
Object stats = equipeService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques des équipes", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DISPONIBILITÉ ===
|
||||
|
||||
@GET
|
||||
@Path("/disponibles")
|
||||
@Operation(summary = "Récupérer les équipes disponibles")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des équipes disponibles récupérée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de date manquants")
|
||||
public Response getEquipesDisponibles(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin,
|
||||
@Parameter(description = "Spécialité requise") @QueryParam("specialite") String specialite) {
|
||||
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<Equipe> equipes = equipeService.findDisponibles(debut, fin, specialite);
|
||||
return Response.ok(equipes).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Format de date invalide: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des équipes disponibles", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des équipes disponibles: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/specialites")
|
||||
@Operation(summary = "Récupérer toutes les spécialités disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Liste des spécialités récupérée avec succès")
|
||||
public Response getAllSpecialites() {
|
||||
try {
|
||||
List<String> specialites = equipeService.findAllSpecialites();
|
||||
return Response.ok(new SpecialitesResponse(specialites)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des spécialités", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des spécialités: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS GESTION ÉQUIPES ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer une nouvelle équipe")
|
||||
@APIResponse(responseCode = "201", description = "Équipe créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createEquipe(
|
||||
@Parameter(description = "Données de la nouvelle équipe") @Valid @NotNull
|
||||
CreateEquipeRequest request) {
|
||||
try {
|
||||
Equipe equipe =
|
||||
equipeService.createEquipe(
|
||||
request.nom,
|
||||
request.specialite,
|
||||
request.description,
|
||||
request.chefEquipeId,
|
||||
request.membresIds);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(equipe).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 de l'équipe", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de l'équipe: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Modifier une équipe")
|
||||
@APIResponse(responseCode = "200", description = "Équipe modifiée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateEquipe(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouvelles données de l'équipe") @Valid @NotNull
|
||||
UpdateEquipeRequest request) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
Equipe equipe =
|
||||
equipeService.updateEquipe(
|
||||
equipeId, request.nom, request.specialite, request.description, request.chefEquipeId);
|
||||
|
||||
return Response.ok(equipe).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 modification de l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification de l'équipe: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/statut")
|
||||
@Operation(summary = "Modifier le statut d'une équipe")
|
||||
@APIResponse(responseCode = "200", description = "Statut modifié avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide")
|
||||
public Response updateStatut(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouveau statut") @Valid @NotNull UpdateStatutRequest request) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
StatutEquipe statut = StatutEquipe.valueOf(request.statut.toUpperCase());
|
||||
|
||||
Equipe equipe = equipeService.updateStatut(equipeId, statut);
|
||||
|
||||
return Response.ok(equipe).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Statut invalide: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la modification du statut de l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification du statut: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer une équipe")
|
||||
@APIResponse(responseCode = "204", description = "Équipe supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe non trouvée")
|
||||
@APIResponse(responseCode = "409", description = "Équipe en cours d'utilisation")
|
||||
public Response deleteEquipe(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
equipeService.deleteEquipe(equipeId);
|
||||
|
||||
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 de l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de l'équipe: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS GESTION MEMBRES ===
|
||||
|
||||
@GET
|
||||
@Path("/{id}/members")
|
||||
@Operation(summary = "Récupérer les membres d'une équipe")
|
||||
@APIResponse(responseCode = "200", description = "Liste des membres récupérée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe non trouvée")
|
||||
public Response getEquipeMembers(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
List<Object> membres = equipeService.getMembers(equipeId);
|
||||
|
||||
return Response.ok(new MembersResponse(membres)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID d'équipe invalide: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des membres de l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des membres: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/members")
|
||||
@Operation(summary = "Ajouter un membre à l'équipe")
|
||||
@APIResponse(responseCode = "200", description = "Membre ajouté avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe ou employé non trouvé")
|
||||
@APIResponse(responseCode = "409", description = "Employé déjà membre d'une autre équipe")
|
||||
public Response addMember(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id,
|
||||
@Parameter(description = "ID de l'employé à ajouter") @Valid @NotNull
|
||||
AddMemberRequest request) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
Equipe equipe = equipeService.addMember(equipeId, request.employeId);
|
||||
|
||||
return Response.ok(equipe).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("Conflit: " + e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de l'ajout du membre à l'équipe {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'ajout du membre: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}/members/{employeId}")
|
||||
@Operation(summary = "Retirer un membre de l'équipe")
|
||||
@APIResponse(responseCode = "200", description = "Membre retiré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Équipe ou employé non trouvé")
|
||||
@APIResponse(responseCode = "409", description = "Impossible de retirer le chef d'équipe")
|
||||
public Response removeMember(
|
||||
@Parameter(description = "ID de l'équipe") @PathParam("id") String id,
|
||||
@Parameter(description = "ID de l'employé à retirer") @PathParam("employeId")
|
||||
String employeId) {
|
||||
try {
|
||||
UUID equipeId = UUID.fromString(id);
|
||||
UUID employeUUID = UUID.fromString(employeId);
|
||||
|
||||
Equipe equipe = equipeService.removeMember(equipeId, employeUUID);
|
||||
|
||||
return Response.ok(equipe).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("IDs invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de retirer: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du retrait du membre {} de l'équipe {}", employeId, id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du retrait du membre: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS RECHERCHE OPTIMISÉE ===
|
||||
|
||||
@GET
|
||||
@Path("/optimal")
|
||||
@Operation(summary = "Trouver l'équipe optimale pour un chantier")
|
||||
@APIResponse(responseCode = "200", description = "Équipes optimales trouvées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Critères invalides")
|
||||
public Response findOptimalEquipe(
|
||||
@Parameter(description = "Spécialité requise") @QueryParam("specialite") String specialite,
|
||||
@Parameter(description = "Nombre minimum de membres")
|
||||
@QueryParam("minMembers")
|
||||
@DefaultValue("1")
|
||||
int minMembers,
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin) {
|
||||
try {
|
||||
if (specialite == null || dateDebut == null || dateFin == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Les paramètres spécialité, dateDebut et dateFin sont obligatoires")
|
||||
.build();
|
||||
}
|
||||
|
||||
LocalDate debut = LocalDate.parse(dateDebut);
|
||||
LocalDate fin = LocalDate.parse(dateFin);
|
||||
|
||||
List<Equipe> equipesOptimales =
|
||||
equipeService.findOptimalForChantier(specialite, minMembers, debut, fin);
|
||||
|
||||
return Response.ok(equipesOptimales).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Paramètres invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la recherche d'équipes optimales", 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 SpecialitesResponse(List<String> specialites) {}
|
||||
|
||||
public static record MembersResponse(List<Object> membres) {}
|
||||
|
||||
public static record CreateEquipeRequest(
|
||||
@Parameter(description = "Nom de l'équipe") String nom,
|
||||
@Parameter(description = "Spécialité de l'équipe") String specialite,
|
||||
@Parameter(description = "Description de l'équipe") String description,
|
||||
@Parameter(description = "ID du chef d'équipe") UUID chefEquipeId,
|
||||
@Parameter(description = "Liste des IDs des membres") List<UUID> membresIds) {}
|
||||
|
||||
public static record UpdateEquipeRequest(
|
||||
@Parameter(description = "Nouveau nom") String nom,
|
||||
@Parameter(description = "Nouvelle spécialité") String specialite,
|
||||
@Parameter(description = "Nouvelle description") String description,
|
||||
@Parameter(description = "Nouvel ID du chef d'équipe") UUID chefEquipeId) {}
|
||||
|
||||
public static record UpdateStatutRequest(
|
||||
@Parameter(description = "Nouveau statut") String statut) {}
|
||||
|
||||
public static record AddMemberRequest(
|
||||
@Parameter(description = "ID de l'employé à ajouter") UUID employeId) {}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.FactureService;
|
||||
import dev.lions.btpxpress.application.service.PdfGeneratorService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Facture;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.inject.Inject;
|
||||
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.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 factures - Architecture 2025 MIGRATION: Préservation exacte de
|
||||
* tous les endpoints critiques
|
||||
*/
|
||||
@Path("/api/v1/factures")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Factures", description = "Gestion des factures BTP")
|
||||
// @Authenticated - Désactivé pour les tests
|
||||
public class FactureResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FactureResource.class);
|
||||
|
||||
@Inject FactureService factureService;
|
||||
|
||||
@Inject PdfGeneratorService pdfGeneratorService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer toutes les factures")
|
||||
@APIResponse(responseCode = "200", description = "Liste des factures récupérée avec succès")
|
||||
public Response getAllFactures(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search,
|
||||
@Parameter(description = "ID du client") @QueryParam("clientId") String clientId,
|
||||
@Parameter(description = "ID du chantier") @QueryParam("chantierId") String chantierId) {
|
||||
try {
|
||||
List<Facture> factures;
|
||||
|
||||
if (clientId != null && !clientId.isEmpty()) {
|
||||
factures = factureService.findByClient(UUID.fromString(clientId));
|
||||
} else if (chantierId != null && !chantierId.isEmpty()) {
|
||||
factures = factureService.findByChantier(UUID.fromString(chantierId));
|
||||
} else if (search != null && !search.isEmpty()) {
|
||||
factures = factureService.search(search);
|
||||
} else {
|
||||
factures = factureService.findAll();
|
||||
}
|
||||
|
||||
return Response.ok(factures).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des factures", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des factures: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer une facture par ID")
|
||||
@APIResponse(responseCode = "200", description = "Facture récupérée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
public Response getFactureById(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID factureId = UUID.fromString(id);
|
||||
return factureService
|
||||
.findById(factureId)
|
||||
.map(facture -> Response.ok(facture).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Facture non trouvée avec l'ID: " + id)
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de facture invalide: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de la facture {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la facture: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre de factures")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de factures retourné avec succès")
|
||||
public Response countFactures() {
|
||||
try {
|
||||
long count = factureService.count();
|
||||
return Response.ok(new CountResponse(count)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du comptage des factures", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du comptage des factures: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques des factures")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStats() {
|
||||
try {
|
||||
Object stats = factureService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques des factures", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chiffre-affaires")
|
||||
@Operation(summary = "Calculer le chiffre d'affaires")
|
||||
@APIResponse(responseCode = "200", description = "Chiffre d'affaires calculé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Format de date invalide")
|
||||
public Response getChiffreAffaires(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin) {
|
||||
try {
|
||||
BigDecimal chiffre;
|
||||
|
||||
if (dateDebut != null && dateFin != null) {
|
||||
LocalDate debut = LocalDate.parse(dateDebut);
|
||||
LocalDate fin = LocalDate.parse(dateFin);
|
||||
chiffre = factureService.getChiffreAffairesParPeriode(debut, fin);
|
||||
} else {
|
||||
chiffre = factureService.getChiffreAffaires();
|
||||
}
|
||||
|
||||
return Response.ok(new ChiffreAffairesResponse(chiffre)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du calcul du chiffre d'affaires", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du calcul du chiffre d'affaires: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/echues")
|
||||
@Operation(summary = "Récupérer les factures échues")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des factures échues récupérée avec succès")
|
||||
public Response getFacturesEchues() {
|
||||
try {
|
||||
List<Facture> factures = factureService.findEchues();
|
||||
return Response.ok(factures).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des factures échues", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des factures échues: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/proches-echeance")
|
||||
@Operation(summary = "Récupérer les factures proches de l'échéance")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des factures proches de l'échéance récupérée avec succès")
|
||||
public Response getFacturesProchesEcheance(
|
||||
@Parameter(description = "Nombre de jours avant l'échéance")
|
||||
@QueryParam("jours")
|
||||
@DefaultValue("7")
|
||||
int jours) {
|
||||
try {
|
||||
List<Facture> factures = factureService.findProchesEcheance(jours);
|
||||
return Response.ok(factures).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des factures proches de l'échéance", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
"Erreur lors de la récupération des factures proches de l'échéance: "
|
||||
+ e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================
|
||||
// ENDPOINTS DE GESTION
|
||||
// ===========================================
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer une nouvelle facture")
|
||||
@APIResponse(responseCode = "201", description = "Facture créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createFacture(
|
||||
@Parameter(description = "Données de la facture à créer") @NotNull
|
||||
CreateFactureRequest request) {
|
||||
try {
|
||||
Facture facture =
|
||||
factureService.create(
|
||||
request.numero,
|
||||
request.clientId,
|
||||
request.chantierId,
|
||||
request.montantHT,
|
||||
request.description);
|
||||
return Response.status(Response.Status.CREATED).entity(facture).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 de la facture", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de la facture: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour une facture")
|
||||
@APIResponse(responseCode = "200", description = "Facture mise à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
public Response updateFacture(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") String id,
|
||||
@Parameter(description = "Données de mise à jour de la facture") @NotNull
|
||||
UpdateFactureRequest request) {
|
||||
try {
|
||||
UUID factureId = UUID.fromString(id);
|
||||
Facture facture =
|
||||
factureService.update(
|
||||
factureId, request.description, request.montantHT, request.dateEcheance);
|
||||
return Response.ok(facture).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 de la facture {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de la facture: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer une facture")
|
||||
@APIResponse(responseCode = "204", description = "Facture supprimée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "ID invalide")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
public Response deleteFacture(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID factureId = UUID.fromString(id);
|
||||
factureService.delete(factureId);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID invalide: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression de la facture {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de la facture: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================
|
||||
// ENDPOINTS DE RECHERCHE AVANCÉE
|
||||
// ===========================================
|
||||
|
||||
@GET
|
||||
@Path("/date-range")
|
||||
@Operation(summary = "Récupérer les factures par plage de dates")
|
||||
@APIResponse(responseCode = "200", description = "Liste des factures récupérée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de date invalides")
|
||||
public Response getFacturesByDateRange(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @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<Facture> factures = factureService.findByDateRange(debut, fin);
|
||||
return Response.ok(factures).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();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/generate-numero")
|
||||
@Operation(summary = "Générer un numéro de facture")
|
||||
@APIResponse(responseCode = "200", description = "Numéro généré avec succès")
|
||||
public Response generateNumero() {
|
||||
try {
|
||||
String numero = factureService.generateNextNumero();
|
||||
return Response.ok(new NumeroResponse(numero)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération du numéro de facture", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération du numéro: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================
|
||||
// CLASSES UTILITAIRES
|
||||
// ===========================================
|
||||
|
||||
public static record CountResponse(long count) {}
|
||||
|
||||
public static record ChiffreAffairesResponse(BigDecimal montant) {}
|
||||
|
||||
public static record NumeroResponse(String numero) {}
|
||||
|
||||
public static record CreateFactureRequest(
|
||||
String numero, UUID clientId, UUID chantierId, BigDecimal montantHT, String description) {}
|
||||
|
||||
public static record UpdateFactureRequest(
|
||||
String description, BigDecimal montantHT, LocalDate dateEcheance) {}
|
||||
|
||||
// === ENDPOINTS WORKFLOW ET STATUTS ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/statut")
|
||||
@Operation(summary = "Mettre à jour le statut d'une facture")
|
||||
@APIResponse(responseCode = "200", description = "Statut mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Transition de statut invalide")
|
||||
public Response updateFactureStatut(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Nouveau statut") @QueryParam("statut") @NotNull
|
||||
Facture.StatutFacture statut) {
|
||||
|
||||
logger.debug("PUT /factures/{}/statut - nouveau statut: {}", id, statut);
|
||||
|
||||
Facture updatedFacture = factureService.updateStatut(id, statut);
|
||||
return Response.ok(updatedFacture).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/payer")
|
||||
@Operation(summary = "Marquer une facture comme payée")
|
||||
@APIResponse(responseCode = "200", description = "Facture marquée comme payée")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Facture ne peut pas être marquée comme payée")
|
||||
public Response marquerFacturePayee(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("PUT /factures/{}/payer", id);
|
||||
|
||||
Facture facturePayee = factureService.marquerPayee(id);
|
||||
return Response.ok(facturePayee).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS CONVERSION DEVIS ===
|
||||
|
||||
@POST
|
||||
@Path("/from-devis/{devisId}")
|
||||
@Operation(summary = "Créer une facture à partir d'un devis")
|
||||
@APIResponse(responseCode = "201", description = "Facture créée à partir du devis")
|
||||
@APIResponse(responseCode = "404", description = "Devis non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Devis ne peut pas être converti")
|
||||
public Response createFactureFromDevis(
|
||||
@Parameter(description = "ID du devis") @PathParam("devisId") UUID devisId) {
|
||||
|
||||
logger.debug("POST /factures/from-devis/{}", devisId);
|
||||
|
||||
Facture facture = factureService.createFromDevis(devisId);
|
||||
return Response.status(Response.Status.CREATED).entity(facture).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS RECHERCHE PAR STATUT ===
|
||||
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(summary = "Récupérer les factures par statut")
|
||||
@APIResponse(responseCode = "200", description = "Factures par statut récupérées")
|
||||
public Response getFacturesByStatut(
|
||||
@Parameter(description = "Statut des factures") @PathParam("statut")
|
||||
Facture.StatutFacture statut) {
|
||||
|
||||
logger.debug("GET /factures/statut/{}", statut);
|
||||
|
||||
List<Facture> factures = factureService.findByStatut(statut);
|
||||
return Response.ok(factures).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/brouillons")
|
||||
@Operation(summary = "Récupérer les factures brouillons")
|
||||
@APIResponse(responseCode = "200", description = "Factures brouillons récupérées")
|
||||
public Response getFacturesBrouillons() {
|
||||
logger.debug("GET /factures/brouillons");
|
||||
|
||||
List<Facture> factures = factureService.findBrouillons();
|
||||
return Response.ok(factures).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/envoyees")
|
||||
@Operation(summary = "Récupérer les factures envoyées")
|
||||
@APIResponse(responseCode = "200", description = "Factures envoyées récupérées")
|
||||
public Response getFacturesEnvoyees() {
|
||||
logger.debug("GET /factures/envoyees");
|
||||
|
||||
List<Facture> factures = factureService.findEnvoyees();
|
||||
return Response.ok(factures).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/payees")
|
||||
@Operation(summary = "Récupérer les factures payées")
|
||||
@APIResponse(responseCode = "200", description = "Factures payées récupérées")
|
||||
public Response getFacturesPayees() {
|
||||
logger.debug("GET /factures/payees");
|
||||
|
||||
List<Facture> factures = factureService.findPayees();
|
||||
return Response.ok(factures).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
@Operation(summary = "Récupérer les factures en retard")
|
||||
@APIResponse(responseCode = "200", description = "Factures en retard récupérées")
|
||||
public Response getFacturesEnRetard() {
|
||||
logger.debug("GET /factures/en-retard");
|
||||
|
||||
List<Facture> factures = factureService.findEnRetard();
|
||||
return Response.ok(factures).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS PDF ===
|
||||
|
||||
@GET
|
||||
@Path("/{id}/pdf")
|
||||
@Operation(summary = "Générer le PDF d'une facture")
|
||||
@APIResponse(responseCode = "200", description = "PDF généré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Facture non trouvée")
|
||||
public Response generateFacturePdf(
|
||||
@Parameter(description = "ID de la facture") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("GET /factures/{}/pdf", id);
|
||||
|
||||
Facture facture =
|
||||
factureService
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new jakarta.ws.rs.NotFoundException("Facture non trouvée"));
|
||||
|
||||
byte[] pdfContent = pdfGeneratorService.generateFacturePdf(facture);
|
||||
String fileName = pdfGeneratorService.generateFileName("facture", facture.getNumero());
|
||||
|
||||
return Response.ok(pdfContent)
|
||||
.header("Content-Type", "application/pdf")
|
||||
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/** Endpoint léger pour les health checks frontend Optimisé pour des vérifications fréquentes */
|
||||
@Path("/api/v1/health")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class HealthResource {
|
||||
|
||||
@GET
|
||||
public Response health() {
|
||||
// Réponse ultra-légère pour minimiser l'impact
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"status", "UP",
|
||||
"timestamp", LocalDateTime.now().toString(),
|
||||
"service", "btpxpress-server"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.MaintenanceService;
|
||||
import dev.lions.btpxpress.domain.core.entity.MaintenanceMateriel;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutMaintenance;
|
||||
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.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
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 maintenances - Architecture 2025 MAINTENANCE: API complète de
|
||||
* maintenance du matériel BTP
|
||||
*/
|
||||
@Path("/api/v1/maintenances")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Maintenances", description = "Gestion de la maintenance du matériel BTP")
|
||||
public class MaintenanceResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MaintenanceResource.class);
|
||||
|
||||
@Inject MaintenanceService maintenanceService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les maintenances",
|
||||
description = "Récupère la liste paginée de toutes les maintenances avec filtres optionnels")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des maintenances récupérée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getAllMaintenances(
|
||||
@Parameter(description = "Numéro de page (0-indexé)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Filtrer par matériel (UUID)") @QueryParam("materielId")
|
||||
UUID materielId,
|
||||
@Parameter(description = "Filtrer par type de maintenance") @QueryParam("type") String type,
|
||||
@Parameter(description = "Filtrer par statut") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Filtrer par technicien") @QueryParam("technicien")
|
||||
String technicien,
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("search") String search) {
|
||||
|
||||
logger.debug("Récupération des maintenances - page: {}, taille: {}", page, size);
|
||||
|
||||
List<MaintenanceMateriel> maintenances;
|
||||
|
||||
if (search != null || type != null || statut != null || technicien != null) {
|
||||
maintenances = maintenanceService.search(search, type, statut, technicien);
|
||||
} else if (materielId != null) {
|
||||
maintenances = maintenanceService.findByMaterielId(materielId);
|
||||
} else {
|
||||
maintenances = maintenanceService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une maintenance par ID",
|
||||
description = "Récupère les détails d'une maintenance spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenance trouvée",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "404", description = "Maintenance non trouvée")
|
||||
public Response getMaintenanceById(
|
||||
@Parameter(description = "Identifiant unique de la maintenance", required = true)
|
||||
@PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération de la maintenance avec l'ID: {}", id);
|
||||
|
||||
return maintenanceService
|
||||
.findById(id)
|
||||
.map(maintenance -> Response.ok(maintenance).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/planifiees")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances planifiées",
|
||||
description = "Récupère toutes les maintenances planifiées")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances planifiées récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesPlanifiees() {
|
||||
logger.debug("Récupération des maintenances planifiées");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findPlanifiees();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-cours")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances en cours",
|
||||
description = "Récupère toutes les maintenances actuellement en cours")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances en cours récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesEnCours() {
|
||||
logger.debug("Récupération des maintenances en cours");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findEnCours();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/terminees")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances terminées",
|
||||
description = "Récupère toutes les maintenances terminées")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances terminées récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesTerminees() {
|
||||
logger.debug("Récupération des maintenances terminées");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findTerminees();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances en retard",
|
||||
description = "Récupère toutes les maintenances planifiées en retard")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances en retard récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesEnRetard() {
|
||||
logger.debug("Récupération des maintenances en retard");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findEnRetard();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/prochaines")
|
||||
@Operation(
|
||||
summary = "Lister les prochaines maintenances",
|
||||
description = "Récupère les maintenances planifiées dans les prochains jours")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Prochaines maintenances récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getProchainesMaintenances(
|
||||
@Parameter(description = "Nombre de jours à venir", example = "30")
|
||||
@QueryParam("jours")
|
||||
@DefaultValue("30")
|
||||
int jours) {
|
||||
|
||||
logger.debug("Récupération des prochaines maintenances dans {} jours", jours);
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findProchainesMaintenances(jours);
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/preventives")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances préventives",
|
||||
description = "Récupère toutes les maintenances préventives")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances préventives récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesPreventives() {
|
||||
logger.debug("Récupération des maintenances préventives");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findMaintenancesPreventives();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/correctives")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances correctives",
|
||||
description = "Récupère toutes les maintenances correctives")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances correctives récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesCorrectives() {
|
||||
logger.debug("Récupération des maintenances correctives");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findMaintenancesCorrectives();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/periode")
|
||||
@Operation(
|
||||
summary = "Lister les maintenances pour une période",
|
||||
description = "Récupère toutes les maintenances dans une période donnée")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenances de la période récupérées",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaintenancesPourPeriode(
|
||||
@Parameter(description = "Date de début (yyyy-mm-dd)", required = true)
|
||||
@QueryParam("dateDebut")
|
||||
@NotNull
|
||||
LocalDate dateDebut,
|
||||
@Parameter(description = "Date de fin (yyyy-mm-dd)", required = true)
|
||||
@QueryParam("dateFin")
|
||||
@NotNull
|
||||
LocalDate dateFin) {
|
||||
|
||||
logger.debug("Récupération des maintenances pour la période {} - {}", dateDebut, dateFin);
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findByDateRange(dateDebut, dateFin);
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION CRUD ===
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle maintenance",
|
||||
description = "Créé une nouvelle maintenance pour un matériel")
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Maintenance créée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createMaintenance(@Valid @NotNull CreateMaintenanceRequest request) {
|
||||
|
||||
logger.info("Création d'une nouvelle maintenance pour le matériel: {}", request.materielId);
|
||||
|
||||
MaintenanceMateriel maintenance =
|
||||
maintenanceService.createMaintenance(
|
||||
request.materielId,
|
||||
request.type,
|
||||
request.description,
|
||||
request.datePrevue,
|
||||
request.technicien,
|
||||
request.notes);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(maintenance).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour une maintenance",
|
||||
description = "Met à jour les informations d'une maintenance existante")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenance mise à jour avec succès",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "404", description = "Maintenance non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateMaintenance(
|
||||
@Parameter(description = "Identifiant de la maintenance", required = true) @PathParam("id")
|
||||
UUID id,
|
||||
@Valid @NotNull UpdateMaintenanceRequest request) {
|
||||
|
||||
logger.info("Mise à jour de la maintenance: {}", id);
|
||||
|
||||
MaintenanceMateriel maintenance =
|
||||
maintenanceService.updateMaintenance(
|
||||
id,
|
||||
request.description,
|
||||
request.datePrevue,
|
||||
request.technicien,
|
||||
request.notes,
|
||||
request.cout);
|
||||
|
||||
return Response.ok(maintenance).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/statut")
|
||||
@Operation(
|
||||
summary = "Mettre à jour le statut d'une maintenance",
|
||||
description = "Change le statut d'une maintenance existante")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Statut mis à jour avec succès",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "404", description = "Maintenance non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Transition de statut invalide")
|
||||
public Response updateStatutMaintenance(
|
||||
@Parameter(description = "Identifiant de la maintenance", required = true) @PathParam("id")
|
||||
UUID id,
|
||||
@Valid @NotNull UpdateStatutRequest request) {
|
||||
|
||||
logger.info("Mise à jour du statut de la maintenance: {}", id);
|
||||
|
||||
StatutMaintenance statut = StatutMaintenance.valueOf(request.statut.toUpperCase());
|
||||
MaintenanceMateriel maintenance = maintenanceService.updateStatut(id, statut);
|
||||
|
||||
return Response.ok(maintenance).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/terminer")
|
||||
@Operation(
|
||||
summary = "Terminer une maintenance",
|
||||
description = "Marque une maintenance comme terminée avec les détails finaux")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Maintenance terminée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "404", description = "Maintenance non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Maintenance déjà terminée")
|
||||
public Response terminerMaintenance(
|
||||
@Parameter(description = "Identifiant de la maintenance", required = true) @PathParam("id")
|
||||
UUID id,
|
||||
@Valid @NotNull TerminerMaintenanceRequest request) {
|
||||
|
||||
logger.info("Finalisation de la maintenance: {}", id);
|
||||
|
||||
MaintenanceMateriel maintenance =
|
||||
maintenanceService.terminerMaintenance(
|
||||
id, request.dateRealisee, request.cout, request.notes);
|
||||
|
||||
return Response.ok(maintenance).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une maintenance",
|
||||
description = "Supprime définitivement une maintenance")
|
||||
@APIResponse(responseCode = "204", description = "Maintenance supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Maintenance non trouvée")
|
||||
@APIResponse(responseCode = "400", description = "Impossible de supprimer")
|
||||
public Response deleteMaintenance(
|
||||
@Parameter(description = "Identifiant de la maintenance", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Suppression de la maintenance: {}", id);
|
||||
|
||||
maintenanceService.deleteMaintenance(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS BUSINESS ===
|
||||
|
||||
@GET
|
||||
@Path("/attention-requise")
|
||||
@Operation(
|
||||
summary = "Matériel nécessitant une attention",
|
||||
description = "Récupère le matériel nécessitant une attention immédiate")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Matériel nécessitant attention récupéré",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
public Response getMaterielRequiringAttention() {
|
||||
logger.debug("Récupération du matériel nécessitant attention");
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.getMaterielRequiringAttention();
|
||||
return Response.ok(maintenances).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/materiel/{materielId}/derniere")
|
||||
@Operation(
|
||||
summary = "Dernière maintenance d'un matériel",
|
||||
description = "Récupère la dernière maintenance effectuée sur un matériel")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Dernière maintenance trouvée",
|
||||
content = @Content(schema = @Schema(implementation = MaintenanceMateriel.class)))
|
||||
@APIResponse(responseCode = "404", description = "Aucune maintenance trouvée")
|
||||
public Response getLastMaintenanceForMateriel(
|
||||
@Parameter(description = "Identifiant du matériel", required = true) @PathParam("materielId")
|
||||
UUID materielId) {
|
||||
|
||||
logger.debug("Récupération de la dernière maintenance pour le matériel: {}", materielId);
|
||||
|
||||
return maintenanceService
|
||||
.getLastMaintenanceForMateriel(materielId)
|
||||
.map(maintenance -> Response.ok(maintenance).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/materiel/{materielId}/cout-total")
|
||||
@Operation(
|
||||
summary = "Coût total de maintenance d'un matériel",
|
||||
description = "Calcule le coût total de maintenance d'un matériel")
|
||||
@APIResponse(responseCode = "200", description = "Coût total calculé")
|
||||
public Response getCoutTotalByMateriel(
|
||||
@Parameter(description = "Identifiant du matériel", required = true) @PathParam("materielId")
|
||||
UUID materielId) {
|
||||
|
||||
logger.debug("Calcul du coût total pour le matériel: {}", materielId);
|
||||
|
||||
BigDecimal coutTotalCalcule = maintenanceService.getCoutTotalByMateriel(materielId);
|
||||
|
||||
final UUID materielIdFinal = materielId;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final UUID materielId = materielIdFinal;
|
||||
public final BigDecimal coutTotal = coutTotalCalcule;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/cout-total-periode")
|
||||
@Operation(
|
||||
summary = "Coût total de maintenance pour une période",
|
||||
description = "Calcule le coût total de maintenance pour une période donnée")
|
||||
@APIResponse(responseCode = "200", description = "Coût total calculé")
|
||||
public Response getCoutTotalByPeriode(
|
||||
@Parameter(description = "Date de début", required = true) @QueryParam("dateDebut") @NotNull
|
||||
LocalDate dateDebut,
|
||||
@Parameter(description = "Date de fin", required = true) @QueryParam("dateFin") @NotNull
|
||||
LocalDate dateFin) {
|
||||
|
||||
logger.debug("Calcul du coût total pour la période {} - {}", dateDebut, dateFin);
|
||||
|
||||
BigDecimal coutTotalCalcule = maintenanceService.getCoutTotalByPeriode(dateDebut, dateFin);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final LocalDate periodeDebut = dateDebut;
|
||||
public final LocalDate periodeFin = dateFin;
|
||||
public final BigDecimal coutTotal = coutTotalCalcule;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Obtenir les statistiques des maintenances",
|
||||
description = "Récupère les statistiques globales des maintenances")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getStatistiques() {
|
||||
logger.debug("Récupération des statistiques des maintenances");
|
||||
Object statistiques = maintenanceService.getStatistics();
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-type")
|
||||
@Operation(
|
||||
summary = "Statistiques par type de maintenance",
|
||||
description = "Récupère les statistiques détaillées par type")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par type récupérées")
|
||||
public Response getStatistiquesParType() {
|
||||
logger.debug("Récupération des statistiques par type");
|
||||
List<Object> stats = maintenanceService.getStatsByType();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-statut")
|
||||
@Operation(
|
||||
summary = "Statistiques par statut",
|
||||
description = "Récupère les statistiques par statut de maintenance")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par statut récupérées")
|
||||
public Response getStatistiquesParStatut() {
|
||||
logger.debug("Récupération des statistiques par statut");
|
||||
List<Object> stats = maintenanceService.getStatsByStatut();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/par-technicien")
|
||||
@Operation(
|
||||
summary = "Statistiques par technicien",
|
||||
description = "Récupère les statistiques de maintenance par technicien")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques par technicien récupérées")
|
||||
public Response getStatistiquesParTechnicien() {
|
||||
logger.debug("Récupération des statistiques par technicien");
|
||||
List<Object> stats = maintenanceService.getStatsByTechnicien();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/tendances-cout")
|
||||
@Operation(
|
||||
summary = "Tendances des coûts de maintenance",
|
||||
description = "Récupère les tendances des coûts sur plusieurs mois")
|
||||
@APIResponse(responseCode = "200", description = "Tendances des coûts récupérées")
|
||||
public Response getTendancesCout(
|
||||
@Parameter(description = "Nombre de mois", example = "12")
|
||||
@QueryParam("mois")
|
||||
@DefaultValue("12")
|
||||
int mois) {
|
||||
|
||||
logger.debug("Récupération des tendances de coût sur {} mois", mois);
|
||||
List<Object> tendances = maintenanceService.getCostTrends(mois);
|
||||
return Response.ok(tendances).build();
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreateMaintenanceRequest {
|
||||
@Schema(description = "Identifiant unique du matériel", required = true)
|
||||
public UUID materielId;
|
||||
|
||||
@Schema(
|
||||
description = "Type de maintenance",
|
||||
required = true,
|
||||
enumeration = {"PREVENTIVE", "CORRECTIVE", "REVISION", "CONTROLE_TECHNIQUE", "NETTOYAGE"})
|
||||
public String type;
|
||||
|
||||
@Schema(
|
||||
description = "Description détaillée de la maintenance",
|
||||
required = true,
|
||||
example = "Révision moteur et changement d'huile")
|
||||
public String description;
|
||||
|
||||
@Schema(
|
||||
description = "Date prévue pour la maintenance",
|
||||
required = true,
|
||||
example = "2024-04-15")
|
||||
public LocalDate datePrevue;
|
||||
|
||||
@Schema(description = "Nom du technicien responsable", example = "Jean Dupont")
|
||||
public String technicien;
|
||||
|
||||
@Schema(description = "Notes additionnelles", example = "Prévoir pièces de rechange")
|
||||
public String notes;
|
||||
}
|
||||
|
||||
public static class UpdateMaintenanceRequest {
|
||||
@Schema(description = "Nouvelle description")
|
||||
public String description;
|
||||
|
||||
@Schema(description = "Nouvelle date prévue")
|
||||
public LocalDate datePrevue;
|
||||
|
||||
@Schema(description = "Nouveau technicien")
|
||||
public String technicien;
|
||||
|
||||
@Schema(description = "Nouvelles notes")
|
||||
public String notes;
|
||||
|
||||
@Schema(description = "Coût de la maintenance", example = "150.50")
|
||||
public BigDecimal cout;
|
||||
}
|
||||
|
||||
public static class UpdateStatutRequest {
|
||||
@Schema(
|
||||
description = "Nouveau statut de la maintenance",
|
||||
required = true,
|
||||
enumeration = {"PLANIFIEE", "EN_COURS", "TERMINEE", "REPORTEE", "ANNULEE"})
|
||||
public String statut;
|
||||
}
|
||||
|
||||
public static class TerminerMaintenanceRequest {
|
||||
@Schema(description = "Date de réalisation effective")
|
||||
public LocalDate dateRealisee;
|
||||
|
||||
@Schema(description = "Coût final de la maintenance", example = "175.25")
|
||||
public BigDecimal cout;
|
||||
|
||||
@Schema(description = "Notes finales sur la maintenance")
|
||||
public String notes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.MaterielService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Materiel;
|
||||
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.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 du matériel - Architecture 2025 MIGRATION: Préservation exacte de
|
||||
* toutes les API endpoints et contrats
|
||||
*/
|
||||
@Path("/api/v1/materiels")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Matériels", description = "Gestion des matériels et équipements")
|
||||
@Authenticated
|
||||
public class MaterielResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MaterielResource.class);
|
||||
|
||||
@Inject MaterielService materielService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les matériels")
|
||||
@APIResponse(responseCode = "200", description = "Liste des matériels récupérée avec succès")
|
||||
public Response getAllMateriels(
|
||||
@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 /materiels - page: {}, size: {}", page, size);
|
||||
|
||||
List<Materiel> materiels;
|
||||
if (page == 0 && size == 20) {
|
||||
materiels = materielService.findAll();
|
||||
} else {
|
||||
materiels = materielService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(materiels).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un matériel par ID")
|
||||
@APIResponse(responseCode = "200", description = "Matériel trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Matériel non trouvé")
|
||||
public Response getMaterielById(
|
||||
@Parameter(description = "ID du matériel") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("GET /materiels/{}", id);
|
||||
|
||||
Materiel materiel = materielService.findByIdRequired(id);
|
||||
return Response.ok(materiel).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Rechercher des matériels")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
public Response searchMateriels(
|
||||
@Parameter(description = "Nom du matériel") @QueryParam("nom") String nom,
|
||||
@Parameter(description = "Type") @QueryParam("type") String type,
|
||||
@Parameter(description = "Marque") @QueryParam("marque") String marque,
|
||||
@Parameter(description = "Statut") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Localisation") @QueryParam("localisation") String localisation) {
|
||||
|
||||
logger.debug(
|
||||
"GET /materiels/search - nom: {}, type: {}, marque: {}, statut: {}, localisation: {}",
|
||||
nom,
|
||||
type,
|
||||
marque,
|
||||
statut,
|
||||
localisation);
|
||||
|
||||
List<Materiel> materiels = materielService.search(nom, type, marque, statut, localisation);
|
||||
return Response.ok(materiels).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/disponibles")
|
||||
@Operation(summary = "Récupérer les matériels disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Liste des matériels disponibles")
|
||||
public Response getMaterielsDisponibles(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin,
|
||||
@Parameter(description = "Type de matériel") @QueryParam("type") String type) {
|
||||
|
||||
logger.debug(
|
||||
"GET /materiels/disponibles - dateDebut: {}, dateFin: {}, type: {}",
|
||||
dateDebut,
|
||||
dateFin,
|
||||
type);
|
||||
|
||||
List<Materiel> materiels = materielService.findDisponibles(dateDebut, dateFin, type);
|
||||
return Response.ok(materiels).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/maintenance-prevue")
|
||||
@Operation(summary = "Récupérer les matériels avec maintenance prévue")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des matériels nécessitant une maintenance")
|
||||
public Response getMaterielsMaintenancePrevue(
|
||||
@Parameter(description = "Nombre de jours à venir") @QueryParam("jours") @DefaultValue("30")
|
||||
int jours) {
|
||||
|
||||
logger.debug("GET /materiels/maintenance-prevue - jours: {}", jours);
|
||||
|
||||
List<Materiel> materiels = materielService.findAvecMaintenancePrevue(jours);
|
||||
return Response.ok(materiels).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/by-type/{type}")
|
||||
@Operation(summary = "Récupérer les matériels par type")
|
||||
@APIResponse(responseCode = "200", description = "Liste des matériels du type spécifié")
|
||||
public Response getMaterielsByType(
|
||||
@Parameter(description = "Type de matériel") @PathParam("type") String type) {
|
||||
|
||||
logger.debug("GET /materiels/by-type/{}", type);
|
||||
|
||||
List<Materiel> materiels = materielService.findByType(type);
|
||||
return Response.ok(materiels).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'ACTIONS - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reserve")
|
||||
@Operation(summary = "Réserver un matériel")
|
||||
@APIResponse(responseCode = "200", description = "Matériel réservé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Matériel non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Matériel non disponible")
|
||||
public Response reserverMateriel(
|
||||
@Parameter(description = "ID du matériel") @PathParam("id") UUID id,
|
||||
@Parameter(description = "Date de début de réservation") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin de réservation") @QueryParam("dateFin")
|
||||
String dateFin) {
|
||||
|
||||
logger.debug("POST /materiels/{}/reserve - dateDebut: {}, dateFin: {}", id, dateDebut, dateFin);
|
||||
|
||||
materielService.reserver(id, dateDebut, dateFin);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/liberer")
|
||||
@Operation(summary = "Libérer un matériel")
|
||||
@APIResponse(responseCode = "200", description = "Matériel libéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Matériel non trouvé")
|
||||
public Response libererMateriel(
|
||||
@Parameter(description = "ID du matériel") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("POST /materiels/{}/liberer", id);
|
||||
|
||||
materielService.liberer(id);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS CRUD - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer un nouveau matériel")
|
||||
@APIResponse(responseCode = "201", description = "Matériel créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createMateriel(@Valid @NotNull Materiel materiel) {
|
||||
logger.debug("POST /materiels");
|
||||
logger.info(
|
||||
"Création matériel: nom={}, type={}, marque={}",
|
||||
materiel.getNom(),
|
||||
materiel.getType(),
|
||||
materiel.getMarque());
|
||||
|
||||
try {
|
||||
Materiel createdMateriel = materielService.create(materiel);
|
||||
return Response.status(Response.Status.CREATED).entity(createdMateriel).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du matériel: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un matériel")
|
||||
@APIResponse(responseCode = "200", description = "Matériel mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Matériel non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response updateMateriel(
|
||||
@Parameter(description = "ID du matériel") @PathParam("id") UUID id,
|
||||
@Valid @NotNull Materiel materiel) {
|
||||
|
||||
logger.debug("PUT /materiels/{}", id);
|
||||
|
||||
Materiel updatedMateriel = materielService.update(id, materiel);
|
||||
return Response.ok(updatedMateriel).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer un matériel")
|
||||
@APIResponse(responseCode = "204", description = "Matériel supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Matériel non trouvé")
|
||||
public Response deleteMateriel(
|
||||
@Parameter(description = "ID du matériel") @PathParam("id") UUID id) {
|
||||
|
||||
logger.debug("DELETE /materiels/{}", id);
|
||||
|
||||
materielService.delete(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES - API CONTRACTS PRÉSERVÉS EXACTEMENT ===
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter le nombre de matériels")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de matériels")
|
||||
public Response countMateriels() {
|
||||
logger.debug("GET /materiels/count");
|
||||
|
||||
long count = materielService.count();
|
||||
return Response.ok(count).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Récupérer les statistiques des matériels")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques des matériels")
|
||||
public Response getStats() {
|
||||
logger.debug("GET /materiels/stats");
|
||||
|
||||
var stats = materielService.getStatistics();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/valeur-totale")
|
||||
@Operation(summary = "Récupérer la valeur totale du parc matériel")
|
||||
@APIResponse(responseCode = "200", description = "Valeur totale du parc matériel")
|
||||
public Response getValeurTotale() {
|
||||
logger.debug("GET /materiels/valeur-totale");
|
||||
|
||||
var valeur = materielService.getValeurTotale();
|
||||
return Response.ok(valeur).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.MessageService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Message;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des messages - Architecture 2025 COMMUNICATION: API complète pour
|
||||
* la messagerie BTP
|
||||
*/
|
||||
@Path("/api/v1/messages")
|
||||
@Tag(name = "Messages", description = "Gestion de la messagerie interne")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"USER", "ADMIN", "MANAGER"})
|
||||
public class MessageResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageResource.class);
|
||||
|
||||
@Inject MessageService messageService;
|
||||
|
||||
// === CONSULTATION DES MESSAGES ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Obtenir tous les messages",
|
||||
description = "Récupère la liste de tous les messages actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des messages récupérée")
|
||||
public Response getAllMessages(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
|
||||
logger.info("Récupération des messages - page: {}, taille: {}", page, size);
|
||||
|
||||
List<Message> messages =
|
||||
size > 0 ? messageService.findAll(page, size) : messageService.findAll();
|
||||
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Obtenir un message par ID", description = "Récupère un message spécifique")
|
||||
@APIResponse(responseCode = "200", description = "Message trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Message non trouvé")
|
||||
public Response getMessageById(
|
||||
@PathParam("id") @NotNull @Parameter(description = "ID du message") UUID id) {
|
||||
|
||||
logger.info("Récupération du message: {}", id);
|
||||
|
||||
Message message = messageService.findByIdRequired(id);
|
||||
return Response.ok(message).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/boite-reception/{userId}")
|
||||
@Operation(
|
||||
summary = "Obtenir la boîte de réception",
|
||||
description = "Messages reçus par un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Boîte de réception récupérée")
|
||||
public Response getBoiteReception(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Récupération boîte de réception pour: {}", userId);
|
||||
|
||||
List<Message> messages = messageService.findBoiteReception(userId);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/boite-envoi/{userId}")
|
||||
@Operation(
|
||||
summary = "Obtenir la boîte d'envoi",
|
||||
description = "Messages envoyés par un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Boîte d'envoi récupérée")
|
||||
public Response getBoiteEnvoi(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Récupération boîte d'envoi pour: {}", userId);
|
||||
|
||||
List<Message> messages = messageService.findBoiteEnvoi(userId);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/non-lus/{userId}")
|
||||
@Operation(
|
||||
summary = "Obtenir les messages non lus",
|
||||
description = "Messages non lus d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Messages non lus récupérés")
|
||||
public Response getMessagesNonLus(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Récupération messages non lus pour: {}", userId);
|
||||
|
||||
List<Message> messages = messageService.findNonLus(userId);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/importants/{userId}")
|
||||
@Operation(
|
||||
summary = "Obtenir les messages importants",
|
||||
description = "Messages marqués comme importants")
|
||||
@APIResponse(responseCode = "200", description = "Messages importants récupérés")
|
||||
public Response getMessagesImportants(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Récupération messages importants pour: {}", userId);
|
||||
|
||||
List<Message> messages = messageService.findImportants(userId);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/archives/{userId}")
|
||||
@Operation(
|
||||
summary = "Obtenir les messages archivés",
|
||||
description = "Messages archivés d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Messages archivés récupérés")
|
||||
public Response getMessagesArchives(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Récupération messages archivés pour: {}", userId);
|
||||
|
||||
List<Message> messages = messageService.findArchives(userId);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/conversation/{user1Id}/{user2Id}")
|
||||
@Operation(
|
||||
summary = "Obtenir une conversation",
|
||||
description = "Conversation entre deux utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Conversation récupérée")
|
||||
public Response getConversation(
|
||||
@PathParam("user1Id") @NotNull @Parameter(description = "ID du premier utilisateur")
|
||||
UUID user1Id,
|
||||
@PathParam("user2Id") @NotNull @Parameter(description = "ID du second utilisateur")
|
||||
UUID user2Id) {
|
||||
|
||||
logger.info("Récupération conversation entre {} et {}", user1Id, user2Id);
|
||||
|
||||
List<Message> messages = messageService.findConversation(user1Id, user2Id);
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(
|
||||
summary = "Rechercher des messages",
|
||||
description = "Recherche textuelle dans les messages")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de recherche")
|
||||
public Response rechercherMessages(
|
||||
@QueryParam("terme") @NotNull @Parameter(description = "Terme de recherche") String terme,
|
||||
@QueryParam("userId") @Parameter(description = "ID utilisateur pour filtrer") UUID userId) {
|
||||
|
||||
logger.info("Recherche de messages avec le terme: {}", terme);
|
||||
|
||||
List<Message> messages =
|
||||
userId != null ? messageService.searchForUser(userId, terme) : messageService.search(terme);
|
||||
|
||||
return Response.ok(messages).build();
|
||||
}
|
||||
|
||||
// === ENVOI ET GESTION DES MESSAGES ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Envoyer un message", description = "Crée et envoie un nouveau message")
|
||||
@APIResponse(responseCode = "201", description = "Message envoyé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response envoyerMessage(EnvoyerMessageForm form) {
|
||||
|
||||
logger.info("Envoi d'un message: {}", form.sujet);
|
||||
|
||||
Message message =
|
||||
messageService.envoyerMessage(
|
||||
form.sujet,
|
||||
form.contenu,
|
||||
form.type,
|
||||
form.priorite,
|
||||
form.expediteurId,
|
||||
form.destinataireId,
|
||||
form.chantierId,
|
||||
form.equipeId,
|
||||
form.documentIds);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(message).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{messageId}/repondre")
|
||||
@Operation(
|
||||
summary = "Répondre à un message",
|
||||
description = "Crée une réponse à un message existant")
|
||||
@APIResponse(responseCode = "201", description = "Réponse envoyée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Message parent non trouvé")
|
||||
public Response repondreMessage(
|
||||
@PathParam("messageId") @NotNull @Parameter(description = "ID du message parent")
|
||||
UUID messageId,
|
||||
RepondreMessageForm form) {
|
||||
|
||||
logger.info("Réponse au message: {}", messageId);
|
||||
|
||||
Message reponse =
|
||||
messageService.repondreMessage(
|
||||
messageId, form.contenu, form.expediteurId, form.priorite, form.documentIds);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(reponse).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/diffuser")
|
||||
@Operation(
|
||||
summary = "Diffuser un message",
|
||||
description = "Envoie un message à plusieurs destinataires")
|
||||
@APIResponse(responseCode = "201", description = "Message diffusé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response diffuserMessage(DiffuserMessageForm form) {
|
||||
|
||||
logger.info("Diffusion d'un message à {} destinataires", form.destinataireIds.size());
|
||||
|
||||
List<Message> messages =
|
||||
messageService.diffuserMessage(
|
||||
form.sujet,
|
||||
form.contenu,
|
||||
form.type,
|
||||
form.priorite,
|
||||
form.expediteurId,
|
||||
form.destinataireIds,
|
||||
form.chantierId,
|
||||
form.equipeId,
|
||||
form.documentIds);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(messages).build();
|
||||
}
|
||||
|
||||
// === ACTIONS SUR LES MESSAGES ===
|
||||
|
||||
@PUT
|
||||
@Path("/{messageId}/marquer-lu/{userId}")
|
||||
@Operation(summary = "Marquer comme lu", description = "Marque un message comme lu")
|
||||
@APIResponse(responseCode = "200", description = "Message marqué comme lu")
|
||||
@APIResponse(responseCode = "400", description = "Action non autorisée")
|
||||
@APIResponse(responseCode = "404", description = "Message non trouvé")
|
||||
public Response marquerCommeLu(
|
||||
@PathParam("messageId") @NotNull @Parameter(description = "ID du message") UUID messageId,
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Marquage du message {} comme lu par {}", messageId, userId);
|
||||
|
||||
Message message = messageService.marquerCommeLu(messageId, userId);
|
||||
return Response.ok(message).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/marquer-tous-lus/{userId}")
|
||||
@Operation(
|
||||
summary = "Marquer tous comme lus",
|
||||
description = "Marque tous les messages non lus comme lus")
|
||||
@APIResponse(responseCode = "200", description = "Messages marqués comme lus")
|
||||
public Response marquerTousCommeLus(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Marquage de tous les messages comme lus pour: {}", userId);
|
||||
|
||||
int count = messageService.marquerTousCommeLus(userId);
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final String message = count + " messages marqués comme lus";
|
||||
public final int nombre = count;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{messageId}/marquer-important/{userId}")
|
||||
@Operation(summary = "Marquer comme important", description = "Marque un message comme important")
|
||||
@APIResponse(responseCode = "200", description = "Message marqué comme important")
|
||||
@APIResponse(responseCode = "400", description = "Action non autorisée")
|
||||
@APIResponse(responseCode = "404", description = "Message non trouvé")
|
||||
public Response marquerCommeImportant(
|
||||
@PathParam("messageId") @NotNull @Parameter(description = "ID du message") UUID messageId,
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Marquage du message {} comme important par {}", messageId, userId);
|
||||
|
||||
Message message = messageService.marquerCommeImportant(messageId, userId);
|
||||
return Response.ok(message).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{messageId}/archiver/{userId}")
|
||||
@Operation(summary = "Archiver un message", description = "Archive un message")
|
||||
@APIResponse(responseCode = "200", description = "Message archivé")
|
||||
@APIResponse(responseCode = "400", description = "Action non autorisée")
|
||||
@APIResponse(responseCode = "404", description = "Message non trouvé")
|
||||
public Response archiverMessage(
|
||||
@PathParam("messageId") @NotNull @Parameter(description = "ID du message") UUID messageId,
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Archivage du message {} par {}", messageId, userId);
|
||||
|
||||
Message message = messageService.archiverMessage(messageId, userId);
|
||||
return Response.ok(message).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{messageId}/{userId}")
|
||||
@Operation(summary = "Supprimer un message", description = "Supprime un message (soft delete)")
|
||||
@APIResponse(responseCode = "204", description = "Message supprimé")
|
||||
@APIResponse(responseCode = "400", description = "Action non autorisée")
|
||||
@APIResponse(responseCode = "404", description = "Message non trouvé")
|
||||
public Response supprimerMessage(
|
||||
@PathParam("messageId") @NotNull @Parameter(description = "ID du message") UUID messageId,
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Suppression du message {} par {}", messageId, userId);
|
||||
|
||||
messageService.supprimerMessage(messageId, userId);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
// === STATISTIQUES ET TABLEAUX DE BORD ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Statistiques globales",
|
||||
description = "Statistiques générales de la messagerie")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées")
|
||||
@RolesAllowed({"ADMIN", "MANAGER"})
|
||||
public Response getStatistiques() {
|
||||
|
||||
logger.info("Génération des statistiques globales des messages");
|
||||
|
||||
Object stats = messageService.getStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques/{userId}")
|
||||
@Operation(
|
||||
summary = "Statistiques utilisateur",
|
||||
description = "Statistiques de messagerie d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques utilisateur récupérées")
|
||||
public Response getStatistiquesUser(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Génération des statistiques pour l'utilisateur: {}", userId);
|
||||
|
||||
Object stats = messageService.getStatistiquesUser(userId);
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tableau-bord/{userId}")
|
||||
@Operation(
|
||||
summary = "Tableau de bord utilisateur",
|
||||
description = "Tableau de bord personnalisé de messagerie")
|
||||
@APIResponse(responseCode = "200", description = "Tableau de bord récupéré")
|
||||
public Response getTableauBordUser(
|
||||
@PathParam("userId") @NotNull @Parameter(description = "ID de l'utilisateur") UUID userId) {
|
||||
|
||||
logger.info("Génération du tableau de bord pour l'utilisateur: {}", userId);
|
||||
|
||||
Object dashboard = messageService.getTableauBordUser(userId);
|
||||
return Response.ok(dashboard).build();
|
||||
}
|
||||
|
||||
// === CLASSES DE FORMULAIRES ===
|
||||
|
||||
public static class EnvoyerMessageForm {
|
||||
public String sujet;
|
||||
public String contenu;
|
||||
public String type;
|
||||
public String priorite;
|
||||
public UUID expediteurId;
|
||||
public UUID destinataireId;
|
||||
public UUID chantierId;
|
||||
public UUID equipeId;
|
||||
public List<UUID> documentIds;
|
||||
}
|
||||
|
||||
public static class RepondreMessageForm {
|
||||
public String contenu;
|
||||
public UUID expediteurId;
|
||||
public String priorite;
|
||||
public List<UUID> documentIds;
|
||||
}
|
||||
|
||||
public static class DiffuserMessageForm {
|
||||
public String sujet;
|
||||
public String contenu;
|
||||
public String type;
|
||||
public String priorite;
|
||||
public UUID expediteurId;
|
||||
public List<UUID> destinataireIds;
|
||||
public UUID chantierId;
|
||||
public UUID equipeId;
|
||||
public List<UUID> documentIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.*;
|
||||
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.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
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 les notifications - Architecture 2025 COMMUNICATION: API de gestion des
|
||||
* notifications BTP
|
||||
*/
|
||||
@Path("/api/v1/notifications")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Notifications", description = "Gestion des notifications système BTP")
|
||||
public class NotificationResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NotificationResource.class);
|
||||
|
||||
@Inject NotificationService notificationService;
|
||||
|
||||
@Inject UserService userService;
|
||||
|
||||
@Inject ChantierService chantierService;
|
||||
|
||||
@Inject MaintenanceService maintenanceService;
|
||||
|
||||
// === CONSULTATION DES NOTIFICATIONS ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les notifications",
|
||||
description = "Récupère toutes les notifications avec pagination et filtres")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des notifications récupérée",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
public Response getAllNotifications(
|
||||
@Parameter(description = "Numéro de page (0-indexé)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Filtrer par utilisateur (UUID)") @QueryParam("userId") UUID userId,
|
||||
@Parameter(description = "Filtrer par type de notification") @QueryParam("type")
|
||||
String typeStr,
|
||||
@Parameter(description = "Afficher seulement les non lues")
|
||||
@QueryParam("nonLues")
|
||||
@DefaultValue("false")
|
||||
boolean nonLues,
|
||||
@Parameter(description = "Filtrer par priorité") @QueryParam("priorite") String prioriteStr) {
|
||||
|
||||
logger.debug("Récupération des notifications - page: {}, taille: {}", page, size);
|
||||
|
||||
TypeNotification type = parseTypeNotification(typeStr);
|
||||
PrioriteNotification priorite = parsePrioriteNotification(prioriteStr);
|
||||
|
||||
List<Notification> notifications;
|
||||
|
||||
if (userId != null) {
|
||||
if (nonLues) {
|
||||
notifications = notificationService.findNonLuesByUser(userId);
|
||||
} else {
|
||||
notifications = notificationService.findByUser(userId);
|
||||
}
|
||||
} else if (type != null) {
|
||||
notifications = notificationService.findByType(type);
|
||||
} else if (priorite != null) {
|
||||
notifications = notificationService.findByPriorite(priorite);
|
||||
} else if (nonLues) {
|
||||
notifications = notificationService.findNonLues();
|
||||
} else {
|
||||
notifications = notificationService.findAll(page, size);
|
||||
}
|
||||
|
||||
return Response.ok(notifications).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une notification par ID",
|
||||
description = "Récupère les détails d'une notification spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notification trouvée",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
@APIResponse(responseCode = "404", description = "Notification non trouvée")
|
||||
public Response getNotificationById(
|
||||
@Parameter(description = "Identifiant unique de la notification", required = true)
|
||||
@PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération de la notification avec l'ID: {}", id);
|
||||
|
||||
return notificationService
|
||||
.findById(id)
|
||||
.map(notification -> Response.ok(notification).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/user/{userId}")
|
||||
@Operation(
|
||||
summary = "Notifications d'un utilisateur",
|
||||
description = "Récupère toutes les notifications d'un utilisateur spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notifications de l'utilisateur récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
public Response getNotificationsByUser(
|
||||
@Parameter(description = "Identifiant de l'utilisateur", required = true) @PathParam("userId")
|
||||
UUID userId,
|
||||
@Parameter(description = "Afficher seulement les non lues")
|
||||
@QueryParam("nonLues")
|
||||
@DefaultValue("false")
|
||||
boolean nonLues) {
|
||||
|
||||
logger.debug("Récupération des notifications pour l'utilisateur: {}", userId);
|
||||
|
||||
List<Notification> notifications;
|
||||
if (nonLues) {
|
||||
notifications = notificationService.findNonLuesByUser(userId);
|
||||
} else {
|
||||
notifications = notificationService.findByUser(userId);
|
||||
}
|
||||
|
||||
return Response.ok(notifications).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/non-lues")
|
||||
@Operation(
|
||||
summary = "Notifications non lues",
|
||||
description = "Récupère toutes les notifications non lues du système")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notifications non lues récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
public Response getNotificationsNonLues(
|
||||
@Parameter(description = "Filtrer par utilisateur (UUID)") @QueryParam("userId")
|
||||
UUID userId) {
|
||||
|
||||
logger.debug("Récupération des notifications non lues");
|
||||
|
||||
List<Notification> notifications;
|
||||
if (userId != null) {
|
||||
notifications = notificationService.findNonLuesByUser(userId);
|
||||
} else {
|
||||
notifications = notificationService.findNonLues();
|
||||
}
|
||||
|
||||
return Response.ok(notifications).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recentes")
|
||||
@Operation(
|
||||
summary = "Notifications récentes",
|
||||
description = "Récupère les notifications les plus récentes")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notifications récentes récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
public Response getNotificationsRecentes(
|
||||
@Parameter(description = "Nombre de notifications à retourner", example = "10")
|
||||
@QueryParam("limite")
|
||||
@DefaultValue("10")
|
||||
int limite,
|
||||
@Parameter(description = "Filtrer par utilisateur (UUID)") @QueryParam("userId")
|
||||
UUID userId) {
|
||||
|
||||
logger.debug("Récupération des {} notifications les plus récentes", limite);
|
||||
|
||||
List<Notification> notifications;
|
||||
if (userId != null) {
|
||||
notifications = notificationService.findRecentsByUser(userId, limite);
|
||||
} else {
|
||||
notifications = notificationService.findRecentes(limite);
|
||||
}
|
||||
|
||||
return Response.ok(notifications).build();
|
||||
}
|
||||
|
||||
// === CRÉATION ET ENVOI DE NOTIFICATIONS ===
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle notification",
|
||||
description = "Crée et envoie une nouvelle notification")
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Notification créée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createNotification(@Valid @NotNull CreateNotificationRequest request) {
|
||||
|
||||
logger.info("Création d'une nouvelle notification: {}", request.titre);
|
||||
|
||||
Notification notification =
|
||||
notificationService.createNotification(
|
||||
request.titre,
|
||||
request.message,
|
||||
request.type,
|
||||
request.priorite,
|
||||
request.userId,
|
||||
request.chantierId,
|
||||
request.lienAction,
|
||||
request.donnees);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(notification).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/broadcast")
|
||||
@Operation(
|
||||
summary = "Diffuser une notification",
|
||||
description = "Envoie une notification à tous les utilisateurs ou à un groupe spécifique")
|
||||
@APIResponse(responseCode = "201", description = "Notification diffusée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response broadcastNotification(@Valid @NotNull BroadcastNotificationRequest request) {
|
||||
|
||||
logger.info("Diffusion d'une notification: {}", request.titre);
|
||||
|
||||
List<Notification> notifications =
|
||||
notificationService.broadcastNotification(
|
||||
request.titre,
|
||||
request.message,
|
||||
request.type,
|
||||
request.priorite,
|
||||
request.userIds,
|
||||
request.roleTarget,
|
||||
request.lienAction,
|
||||
request.donnees);
|
||||
|
||||
final int nombreNotificationsBroadcast = notifications.size();
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
new Object() {
|
||||
public final int nombreNotifications = nombreNotificationsBroadcast;
|
||||
public final List<Notification> notificationsList = notifications;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/automatiques/maintenance")
|
||||
@Operation(
|
||||
summary = "Générer notifications de maintenance",
|
||||
description = "Génère automatiquement les notifications de maintenance en retard")
|
||||
@APIResponse(responseCode = "201", description = "Notifications de maintenance générées")
|
||||
public Response generateMaintenanceNotifications() {
|
||||
|
||||
logger.info("Génération des notifications de maintenance automatiques");
|
||||
|
||||
List<Notification> notifications = notificationService.generateMaintenanceNotifications();
|
||||
|
||||
final int nombreNotificationsGenere = notifications.size();
|
||||
final String messageReponse = "Notifications de maintenance générées";
|
||||
final List<Object> detailsNotifications =
|
||||
notifications.stream()
|
||||
.map(
|
||||
n ->
|
||||
new Object() {
|
||||
public final String titre = n.getTitre();
|
||||
public final String priorite = n.getPriorite().toString();
|
||||
public final String destinataire = n.getUser().getEmail();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
new Object() {
|
||||
public final int nombreNotifications = nombreNotificationsGenere;
|
||||
public final String message = messageReponse;
|
||||
public final List<Object> details = detailsNotifications;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/automatiques/chantiers")
|
||||
@Operation(
|
||||
summary = "Générer notifications de chantiers",
|
||||
description =
|
||||
"Génère automatiquement les notifications pour les chantiers en retard ou critiques")
|
||||
@APIResponse(responseCode = "201", description = "Notifications de chantiers générées")
|
||||
public Response generateChantierNotifications() {
|
||||
|
||||
logger.info("Génération des notifications de chantiers automatiques");
|
||||
|
||||
List<Notification> notifications = notificationService.generateChantierNotifications();
|
||||
|
||||
final int nombreNotificationsChantier = notifications.size();
|
||||
final String messageChantier = "Notifications de chantiers générées";
|
||||
final List<Object> detailsChantier =
|
||||
notifications.stream()
|
||||
.map(
|
||||
n ->
|
||||
new Object() {
|
||||
public final String titre = n.getTitre();
|
||||
public final String priorite = n.getPriorite().toString();
|
||||
public final String destinataire = n.getUser().getEmail();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
new Object() {
|
||||
public final int nombreNotifications = nombreNotificationsChantier;
|
||||
public final String message = messageChantier;
|
||||
public final List<Object> details = detailsChantier;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === GESTION DES NOTIFICATIONS ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/marquer-lue")
|
||||
@Operation(
|
||||
summary = "Marquer une notification comme lue",
|
||||
description = "Change le statut d'une notification à 'lue'")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notification marquée comme lue",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
@APIResponse(responseCode = "404", description = "Notification non trouvée")
|
||||
public Response marquerCommeLue(
|
||||
@Parameter(description = "Identifiant de la notification", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Marquage de la notification comme lue: {}", id);
|
||||
|
||||
Notification notification = notificationService.marquerCommeLue(id);
|
||||
return Response.ok(notification).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/marquer-non-lue")
|
||||
@Operation(
|
||||
summary = "Marquer une notification comme non lue",
|
||||
description = "Change le statut d'une notification à 'non lue'")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Notification marquée comme non lue",
|
||||
content = @Content(schema = @Schema(implementation = Notification.class)))
|
||||
@APIResponse(responseCode = "404", description = "Notification non trouvée")
|
||||
public Response marquerCommeNonLue(
|
||||
@Parameter(description = "Identifiant de la notification", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Marquage de la notification comme non lue: {}", id);
|
||||
|
||||
Notification notification = notificationService.marquerCommeNonLue(id);
|
||||
return Response.ok(notification).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/user/{userId}/marquer-toutes-lues")
|
||||
@Operation(
|
||||
summary = "Marquer toutes les notifications d'un utilisateur comme lues",
|
||||
description = "Marque toutes les notifications non lues d'un utilisateur comme lues")
|
||||
@APIResponse(responseCode = "200", description = "Toutes les notifications marquées comme lues")
|
||||
public Response marquerToutesCommeLues(
|
||||
@Parameter(description = "Identifiant de l'utilisateur", required = true) @PathParam("userId")
|
||||
UUID userId) {
|
||||
|
||||
logger.info("Marquage de toutes les notifications comme lues pour l'utilisateur: {}", userId);
|
||||
|
||||
int nombreMises = notificationService.marquerToutesCommeLues(userId);
|
||||
|
||||
final int nombreMisesFinal = nombreMises;
|
||||
final String messageMises = "Toutes les notifications ont été marquées comme lues";
|
||||
final UUID userIdFinal = userId;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final int nombreNotificationsMises = nombreMisesFinal;
|
||||
public final String message = messageMises;
|
||||
public final UUID userId = userIdFinal;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une notification",
|
||||
description = "Supprime définitivement une notification")
|
||||
@APIResponse(responseCode = "204", description = "Notification supprimée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Notification non trouvée")
|
||||
public Response deleteNotification(
|
||||
@Parameter(description = "Identifiant de la notification", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.info("Suppression de la notification: {}", id);
|
||||
|
||||
notificationService.deleteNotification(id);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/user/{userId}/anciennes")
|
||||
@Operation(
|
||||
summary = "Supprimer les anciennes notifications",
|
||||
description = "Supprime les notifications anciennes d'un utilisateur (plus de X jours)")
|
||||
@APIResponse(responseCode = "200", description = "Anciennes notifications supprimées")
|
||||
public Response deleteAnciennesNotifications(
|
||||
@Parameter(description = "Identifiant de l'utilisateur", required = true) @PathParam("userId")
|
||||
UUID userId,
|
||||
@Parameter(description = "Nombre de jours (défaut: 30)", example = "30")
|
||||
@QueryParam("jours")
|
||||
@DefaultValue("30")
|
||||
int jours) {
|
||||
|
||||
logger.info(
|
||||
"Suppression des anciennes notifications (plus de {} jours) pour l'utilisateur: {}",
|
||||
jours,
|
||||
userId);
|
||||
|
||||
int nombreSupprimees = notificationService.deleteAnciennesNotifications(userId, jours);
|
||||
|
||||
final int nombreSupprimeesFinal = nombreSupprimees;
|
||||
final String messageSuppr = "Anciennes notifications supprimées";
|
||||
final int joursLimiteFinal = jours;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final int nombreNotificationsSupprimees = nombreSupprimeesFinal;
|
||||
public final String message = messageSuppr;
|
||||
public final int joursLimite = joursLimiteFinal;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === STATISTIQUES ET MÉTRIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Statistiques des notifications",
|
||||
description = "Récupère les statistiques globales des notifications")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées")
|
||||
public Response getStatistiques(
|
||||
@Parameter(description = "Filtrer par utilisateur (UUID)") @QueryParam("userId")
|
||||
UUID userId) {
|
||||
|
||||
logger.debug("Récupération des statistiques des notifications");
|
||||
|
||||
Object statistiques;
|
||||
if (userId != null) {
|
||||
statistiques = notificationService.getStatistiquesUser(userId);
|
||||
} else {
|
||||
statistiques = notificationService.getStatistiques();
|
||||
}
|
||||
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tableau-bord")
|
||||
@Operation(
|
||||
summary = "Tableau de bord des notifications",
|
||||
description = "Tableau de bord complet avec métriques et alertes")
|
||||
@APIResponse(responseCode = "200", description = "Tableau de bord récupéré")
|
||||
public Response getTableauBord(
|
||||
@Parameter(description = "Filtrer par utilisateur (UUID)") @QueryParam("userId")
|
||||
UUID userId) {
|
||||
|
||||
logger.debug("Génération du tableau de bord des notifications");
|
||||
|
||||
if (userId != null) {
|
||||
Object tableauBordUser = notificationService.getTableauBordUser(userId);
|
||||
return Response.ok(tableauBordUser).build();
|
||||
} else {
|
||||
Object tableauBordGlobal = notificationService.getTableauBordGlobal();
|
||||
return Response.ok(tableauBordGlobal).build();
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES ===
|
||||
|
||||
private TypeNotification parseTypeNotification(String typeStr) {
|
||||
if (typeStr == null || typeStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return TypeNotification.valueOf(typeStr.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Type de notification invalide: {}", typeStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PrioriteNotification parsePrioriteNotification(String prioriteStr) {
|
||||
if (prioriteStr == null || prioriteStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return PrioriteNotification.valueOf(prioriteStr.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Priorité de notification invalide: {}", prioriteStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class CreateNotificationRequest {
|
||||
@Schema(description = "Titre de la notification", required = true)
|
||||
public String titre;
|
||||
|
||||
@Schema(description = "Message de la notification", required = true)
|
||||
public String message;
|
||||
|
||||
@Schema(
|
||||
description = "Type de notification",
|
||||
required = true,
|
||||
enumeration = {"INFO", "ALERTE", "MAINTENANCE", "CHANTIER", "SYSTEM"})
|
||||
public String type;
|
||||
|
||||
@Schema(
|
||||
description = "Priorité de la notification",
|
||||
enumeration = {"BASSE", "NORMALE", "HAUTE", "CRITIQUE"})
|
||||
public String priorite;
|
||||
|
||||
@Schema(description = "ID de l'utilisateur destinataire", required = true)
|
||||
public UUID userId;
|
||||
|
||||
@Schema(description = "ID du chantier associé (optionnel)")
|
||||
public UUID chantierId;
|
||||
|
||||
@Schema(description = "Lien vers une action (optionnel)")
|
||||
public String lienAction;
|
||||
|
||||
@Schema(description = "Données supplémentaires au format JSON (optionnel)")
|
||||
public String donnees;
|
||||
}
|
||||
|
||||
public static class BroadcastNotificationRequest {
|
||||
@Schema(description = "Titre de la notification", required = true)
|
||||
public String titre;
|
||||
|
||||
@Schema(description = "Message de la notification", required = true)
|
||||
public String message;
|
||||
|
||||
@Schema(
|
||||
description = "Type de notification",
|
||||
required = true,
|
||||
enumeration = {"INFO", "ALERTE", "MAINTENANCE", "CHANTIER", "SYSTEM"})
|
||||
public String type;
|
||||
|
||||
@Schema(
|
||||
description = "Priorité de la notification",
|
||||
enumeration = {"BASSE", "NORMALE", "HAUTE", "CRITIQUE"})
|
||||
public String priorite;
|
||||
|
||||
@Schema(description = "Liste des IDs utilisateurs destinataires (optionnel)")
|
||||
public List<UUID> userIds;
|
||||
|
||||
@Schema(
|
||||
description = "Rôle cible pour diffusion (optionnel)",
|
||||
enumeration = {"ADMIN", "CHEF_CHANTIER", "EMPLOYE", "CLIENT"})
|
||||
public String roleTarget;
|
||||
|
||||
@Schema(description = "Lien vers une action (optionnel)")
|
||||
public String lienAction;
|
||||
|
||||
@Schema(description = "Données supplémentaires au format JSON (optionnel)")
|
||||
public String donnees;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.PhaseChantierService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Chantier;
|
||||
import dev.lions.btpxpress.domain.core.entity.PhaseChantier;
|
||||
import dev.lions.btpxpress.domain.core.entity.StatutPhaseChantier;
|
||||
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.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 phases de chantier */
|
||||
@Path("/api/v1/phases-chantier")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Phases de Chantier", description = "Gestion des phases de chantier BTP")
|
||||
public class PhaseChantierResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PhaseChantierResource.class);
|
||||
|
||||
@Inject PhaseChantierService phaseChantierService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer toutes les phases")
|
||||
@APIResponse(responseCode = "200", description = "Liste des phases récupérée avec succès")
|
||||
public Response getAllPhases(
|
||||
@Parameter(description = "Statut de la phase") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Filtrer par chantiers actifs seulement (true/false)")
|
||||
@QueryParam("chantiersActifs")
|
||||
@DefaultValue("false")
|
||||
boolean chantiersActifs) {
|
||||
try {
|
||||
List<PhaseChantier> phases;
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
phases =
|
||||
phaseChantierService.findByStatut(StatutPhaseChantier.valueOf(statut.toUpperCase()));
|
||||
} else if (chantiersActifs) {
|
||||
phases = phaseChantierService.findAllForActiveChantiers();
|
||||
logger.debug("Récupération de {} phases pour chantiers actifs uniquement", phases.size());
|
||||
} else {
|
||||
phases = phaseChantierService.findAll();
|
||||
logger.debug("Récupération de {} phases (tous chantiers)", phases.size());
|
||||
}
|
||||
|
||||
return Response.ok(phases).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des phases", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des phases: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
@Operation(summary = "Récupérer les phases d'un chantier")
|
||||
@APIResponse(responseCode = "200", description = "Phases du chantier récupérées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "ID de chantier invalide")
|
||||
public Response getPhasesByChantier(
|
||||
@Parameter(description = "ID du chantier") @PathParam("chantierId") String chantierId) {
|
||||
try {
|
||||
UUID chantierUuid = UUID.fromString(chantierId);
|
||||
List<PhaseChantier> phases = phaseChantierService.findByChantier(chantierUuid);
|
||||
logger.debug("Récupération de {} phases pour le chantier {}", phases.size(), chantierId);
|
||||
return Response.ok(phases).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("ID de chantier invalide: {}", chantierId);
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de chantier invalide: " + chantierId)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des phases du chantier {}", chantierId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des phases: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer une phase par ID")
|
||||
@APIResponse(responseCode = "200", description = "Phase récupérée avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Phase non trouvée")
|
||||
public Response getPhaseById(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
PhaseChantier phase = phaseChantierService.findById(phaseId);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
@Operation(summary = "Récupérer les phases en retard")
|
||||
public Response getPhasesEnRetard() {
|
||||
try {
|
||||
List<PhaseChantier> phases = phaseChantierService.findPhasesEnRetard();
|
||||
return Response.ok(phases).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des phases en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des phases en retard: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/en-cours")
|
||||
@Operation(summary = "Récupérer les phases en cours")
|
||||
public Response getPhasesEnCours() {
|
||||
try {
|
||||
List<PhaseChantier> phases = phaseChantierService.findPhasesEnCours();
|
||||
return Response.ok(phases).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des phases en cours", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des phases en cours: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/critiques")
|
||||
@Operation(summary = "Récupérer les phases critiques")
|
||||
public Response getPhasesCritiques() {
|
||||
try {
|
||||
List<PhaseChantier> phases = phaseChantierService.findPhasesCritiques();
|
||||
return Response.ok(phases).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des phases critiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des phases critiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupérer les statistiques des phases")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
Map<String, Object> stats = phaseChantierService.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("Erreur lors de la récupération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MODIFICATION ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer une nouvelle phase")
|
||||
@APIResponse(responseCode = "201", description = "Phase créée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createPhase(@Valid PhaseCreateRequest request) {
|
||||
try {
|
||||
PhaseChantier phase = new PhaseChantier();
|
||||
phase.setNom(request.nom);
|
||||
phase.setDescription(request.description);
|
||||
phase.setOrdreExecution(request.ordreExecution);
|
||||
|
||||
if (request.dateDebutPrevue != null) {
|
||||
phase.setDateDebutPrevue(LocalDate.parse(request.dateDebutPrevue));
|
||||
}
|
||||
if (request.dateFinPrevue != null) {
|
||||
phase.setDateFinPrevue(LocalDate.parse(request.dateFinPrevue));
|
||||
}
|
||||
|
||||
if (request.budgetPrevu != null) {
|
||||
phase.setBudgetPrevu(new BigDecimal(request.budgetPrevu.toString()));
|
||||
}
|
||||
|
||||
// Associer le chantier
|
||||
Chantier chantier = new Chantier();
|
||||
chantier.setId(UUID.fromString(request.chantierId));
|
||||
phase.setChantier(chantier);
|
||||
|
||||
// Associer la phase parente si elle existe (pour les sous-phases)
|
||||
if (request.phaseParentId != null && !request.phaseParentId.trim().isEmpty()) {
|
||||
PhaseChantier phaseParent = new PhaseChantier();
|
||||
phaseParent.setId(UUID.fromString(request.phaseParentId));
|
||||
phase.setPhaseParent(phaseParent);
|
||||
}
|
||||
|
||||
phase.setBloquante(request.critique != null ? request.critique : false);
|
||||
|
||||
PhaseChantier savedPhase = phaseChantierService.create(phase);
|
||||
return Response.status(Response.Status.CREATED).entity(savedPhase).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 de la phase", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour une phase")
|
||||
@APIResponse(responseCode = "200", description = "Phase mise à jour avec succès")
|
||||
public Response updatePhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id,
|
||||
@Valid PhaseCreateRequest request) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
|
||||
PhaseChantier phaseData = new PhaseChantier();
|
||||
phaseData.setNom(request.nom);
|
||||
phaseData.setDescription(request.description);
|
||||
phaseData.setOrdreExecution(request.ordreExecution);
|
||||
|
||||
if (request.dateDebutPrevue != null) {
|
||||
phaseData.setDateDebutPrevue(LocalDate.parse(request.dateDebutPrevue));
|
||||
}
|
||||
if (request.dateFinPrevue != null) {
|
||||
phaseData.setDateFinPrevue(LocalDate.parse(request.dateFinPrevue));
|
||||
}
|
||||
|
||||
if (request.budgetPrevu != null) {
|
||||
phaseData.setBudgetPrevu(new BigDecimal(request.budgetPrevu.toString()));
|
||||
}
|
||||
|
||||
phaseData.setBloquante(request.critique != null ? request.critique : false);
|
||||
|
||||
PhaseChantier updatedPhase = phaseChantierService.update(phaseId, phaseData);
|
||||
return Response.ok(updatedPhase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Données invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer une phase")
|
||||
@APIResponse(responseCode = "204", description = "Phase supprimée avec succès")
|
||||
public Response deletePhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
phaseChantierService.delete(phaseId);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de supprimer la phase: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'ACTIONS ===
|
||||
|
||||
@POST
|
||||
@Path("/{id}/demarrer")
|
||||
@Operation(summary = "Démarrer une phase")
|
||||
public Response demarrerPhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
PhaseChantier phase = phaseChantierService.demarrerPhase(phaseId);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de démarrer la phase: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors du démarrage de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du démarrage de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/terminer")
|
||||
@Operation(summary = "Terminer une phase")
|
||||
public Response terminerPhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
PhaseChantier phase = phaseChantierService.terminerPhase(phaseId);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de terminer la phase: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la finalisation de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la finalisation de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/suspendre")
|
||||
@Operation(summary = "Suspendre une phase")
|
||||
public Response suspendrePhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id,
|
||||
SuspendrePhaseRequest request) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
String motif = request != null ? request.motif : null;
|
||||
PhaseChantier phase = phaseChantierService.suspendrPhase(phaseId, motif);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de suspendre la phase: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suspension de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suspension de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reprendre")
|
||||
@Operation(summary = "Reprendre une phase suspendue")
|
||||
public Response reprendrePhase(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
PhaseChantier phase = phaseChantierService.reprendrePhase(phaseId);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID de phase invalide: " + id)
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity("Impossible de reprendre la phase: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la reprise de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la reprise de la phase: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/avancement")
|
||||
@Operation(summary = "Mettre à jour l'avancement d'une phase")
|
||||
public Response updateAvancement(
|
||||
@Parameter(description = "ID de la phase") @PathParam("id") String id,
|
||||
@NotNull AvancementRequest request) {
|
||||
try {
|
||||
UUID phaseId = UUID.fromString(id);
|
||||
BigDecimal pourcentage = new BigDecimal(request.pourcentage.toString());
|
||||
PhaseChantier phase = phaseChantierService.updateAvancement(phaseId, pourcentage);
|
||||
return Response.ok(phase).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Données invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Phase non trouvée avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour de l'avancement de la phase {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour de l'avancement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class PhaseCreateRequest {
|
||||
public String nom;
|
||||
public String description;
|
||||
public String chantierId;
|
||||
public String dateDebutPrevue;
|
||||
public String dateFinPrevue;
|
||||
public Integer ordreExecution = 1;
|
||||
public Double budgetPrevu;
|
||||
public Boolean critique;
|
||||
public String responsableId;
|
||||
public String phaseParentId;
|
||||
}
|
||||
|
||||
public static class SuspendrePhaseRequest {
|
||||
public String motif;
|
||||
}
|
||||
|
||||
public static class AvancementRequest {
|
||||
public Double pourcentage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.DocumentService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Document;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeDocument;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
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.jboss.resteasy.reactive.PartType;
|
||||
import org.jboss.resteasy.reactive.RestForm;
|
||||
import org.jboss.resteasy.reactive.multipart.FileUpload;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des photos - Architecture 2025 PHOTOS: API spécialisée pour les
|
||||
* photos de chantiers BTP
|
||||
*/
|
||||
@Path("/api/v1/photos")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Photos", description = "Gestion spécialisée des photos de chantiers BTP")
|
||||
public class PhotoResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PhotoResource.class);
|
||||
|
||||
// Types MIME acceptés pour les photos
|
||||
private static final String[] ALLOWED_IMAGE_TYPES = {
|
||||
"image/jpeg", "image/jpg", "image/png", "image/bmp", "image/tiff", "image/webp"
|
||||
};
|
||||
|
||||
// Taille maximale pour les photos (20MB)
|
||||
private static final long MAX_PHOTO_SIZE = 20 * 1024 * 1024;
|
||||
|
||||
@Inject DocumentService documentService;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les photos",
|
||||
description = "Récupère la liste de toutes les photos de chantiers")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des photos récupérée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getAllPhotos(
|
||||
@Parameter(description = "Numéro de page (0-indexé)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Filtrer par chantier (UUID)") @QueryParam("chantierId")
|
||||
UUID chantierId,
|
||||
@Parameter(description = "Filtrer par employé (UUID)") @QueryParam("employeId")
|
||||
UUID employeId,
|
||||
@Parameter(description = "Terme de recherche dans les tags") @QueryParam("tags")
|
||||
String tags) {
|
||||
|
||||
logger.debug("Récupération des photos - page: {}, taille: {}", page, size);
|
||||
|
||||
List<Document> photos;
|
||||
|
||||
if (chantierId != null || employeId != null || tags != null) {
|
||||
photos = documentService.search(tags, "PHOTO_CHANTIER", chantierId, null, null);
|
||||
|
||||
// Filtrage supplémentaire par employé si spécifié
|
||||
if (employeId != null) {
|
||||
photos =
|
||||
photos.stream()
|
||||
.filter(
|
||||
photo ->
|
||||
photo.getEmploye() != null && photo.getEmploye().getId().equals(employeId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else {
|
||||
photos = documentService.findByType(TypeDocument.PHOTO_CHANTIER);
|
||||
|
||||
// Application de la pagination sur la liste complète
|
||||
int fromIndex = page * size;
|
||||
int toIndex = Math.min(fromIndex + size, photos.size());
|
||||
|
||||
if (fromIndex < photos.size()) {
|
||||
photos = photos.subList(fromIndex, toIndex);
|
||||
} else {
|
||||
photos = List.of();
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(photos).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une photo par ID",
|
||||
description = "Récupère les métadonnées d'une photo spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Photo trouvée",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
@APIResponse(responseCode = "404", description = "Photo non trouvée")
|
||||
public Response getPhotoById(
|
||||
@Parameter(description = "Identifiant unique de la photo", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération de la photo avec l'ID: {}", id);
|
||||
|
||||
return documentService
|
||||
.findById(id)
|
||||
.filter(doc -> doc.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.map(photo -> Response.ok(photo).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantier/{chantierId}")
|
||||
@Operation(
|
||||
summary = "Photos d'un chantier",
|
||||
description = "Récupère toutes les photos d'un chantier spécifique")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Photos du chantier récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getPhotosByChantier(
|
||||
@Parameter(description = "Identifiant du chantier", required = true) @PathParam("chantierId")
|
||||
UUID chantierId) {
|
||||
|
||||
logger.debug("Récupération des photos pour le chantier: {}", chantierId);
|
||||
|
||||
List<Document> photos =
|
||||
documentService.findByChantier(chantierId).stream()
|
||||
.filter(doc -> doc.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(photos).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/employe/{employeId}")
|
||||
@Operation(
|
||||
summary = "Photos prises par un employé",
|
||||
description = "Récupère toutes les photos prises par un employé")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Photos de l'employé récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getPhotosByEmploye(
|
||||
@Parameter(description = "Identifiant de l'employé", required = true) @PathParam("employeId")
|
||||
UUID employeId) {
|
||||
|
||||
logger.debug("Récupération des photos pour l'employé: {}", employeId);
|
||||
|
||||
List<Document> photos =
|
||||
documentService.findByEmploye(employeId).stream()
|
||||
.filter(doc -> doc.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(photos).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recentes")
|
||||
@Operation(
|
||||
summary = "Photos récentes",
|
||||
description = "Récupère les photos les plus récemment ajoutées")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Photos récentes récupérées",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
public Response getPhotosRecentes(
|
||||
@Parameter(description = "Nombre de photos à retourner", example = "10")
|
||||
@QueryParam("limite")
|
||||
@DefaultValue("10")
|
||||
int limite) {
|
||||
|
||||
logger.debug("Récupération des {} photos les plus récentes", limite);
|
||||
|
||||
List<Document> photos =
|
||||
documentService
|
||||
.findRecents(limite * 2) // Récupérer plus pour filtrer
|
||||
.stream()
|
||||
.filter(doc -> doc.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.limit(limite)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(photos).build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS D'UPLOAD SPÉCIALISÉS ===
|
||||
|
||||
@POST
|
||||
@Path("/upload")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(
|
||||
summary = "Uploader une photo de chantier",
|
||||
description = "Upload une photo avec optimisations spécifiques aux images")
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Photo uploadée avec succès",
|
||||
content = @Content(schema = @Schema(implementation = Document.class)))
|
||||
@APIResponse(responseCode = "400", description = "Fichier non valide ou trop volumineux")
|
||||
public Response uploadPhoto(
|
||||
@RestForm("nom") String nom,
|
||||
@RestForm("description") String description,
|
||||
@RestForm("file") FileUpload file,
|
||||
@RestForm("fileName") String fileName,
|
||||
@RestForm("contentType") String contentType,
|
||||
@RestForm("chantierId") UUID chantierId,
|
||||
@RestForm("materielId") UUID materielId,
|
||||
@RestForm("equipeId") UUID equipeId,
|
||||
@RestForm("employeId") UUID employeId,
|
||||
@RestForm("localisation") String localisation,
|
||||
@RestForm("latitude") Double latitude,
|
||||
@RestForm("longitude") Double longitude) {
|
||||
|
||||
logger.info("Upload de photo: {}", nom);
|
||||
|
||||
// Validation spécifique aux images
|
||||
validatePhotoUpload(file, fileName, contentType);
|
||||
|
||||
Document photo =
|
||||
documentService.uploadDocument(
|
||||
nom != null ? nom : "Photo_" + LocalDateTime.now(),
|
||||
description,
|
||||
"PHOTO_CHANTIER", // Type fixe pour les photos
|
||||
file,
|
||||
fileName,
|
||||
contentType,
|
||||
file != null ? file.size() : 0L,
|
||||
chantierId,
|
||||
materielId,
|
||||
equipeId,
|
||||
employeId,
|
||||
null, // Pas de client pour les photos de chantier
|
||||
null, // tags - ajouté si besoin
|
||||
false, // estPublic - défaut
|
||||
null); // userId - ajouté si besoin
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(photo).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/upload-multiple")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(
|
||||
summary = "Uploader plusieurs photos",
|
||||
description = "Upload multiple de photos en une seule requête")
|
||||
@APIResponse(responseCode = "201", description = "Photos uploadées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Erreur dans l'upload")
|
||||
public Response uploadMultiplePhotos(
|
||||
@RestForm(FileUpload.ALL) List<FileUpload> files,
|
||||
@RestForm("chantierId") UUID chantierId,
|
||||
@RestForm("description") String description) {
|
||||
|
||||
logger.info("Upload multiple de {} photos", files != null ? files.size() : 0);
|
||||
|
||||
if (files == null || files.isEmpty()) {
|
||||
throw new BadRequestException("Aucun fichier fourni");
|
||||
}
|
||||
|
||||
if (files.size() > 10) {
|
||||
throw new BadRequestException("Maximum 10 fichiers autorisés par upload");
|
||||
}
|
||||
|
||||
List<Document> uploadedPhotos = new java.util.ArrayList<>();
|
||||
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
FileUpload file = files.get(i);
|
||||
String fileName = file.fileName();
|
||||
String contentType = file.contentType();
|
||||
long fileSize = file.size();
|
||||
|
||||
// Validation basique de chaque fichier
|
||||
if (!isValidImageType(contentType)) {
|
||||
throw new BadRequestException("Type de fichier non supporté: " + contentType);
|
||||
}
|
||||
|
||||
Document photo =
|
||||
documentService.uploadDocument(
|
||||
"Photo_multiple_" + fileName,
|
||||
description,
|
||||
"PHOTO_CHANTIER",
|
||||
file,
|
||||
fileName,
|
||||
contentType,
|
||||
fileSize,
|
||||
chantierId,
|
||||
null, // materielId
|
||||
null, // equipeId
|
||||
null, // employeId
|
||||
null, // clientId
|
||||
null, // tags
|
||||
false, // estPublic
|
||||
null); // userId
|
||||
|
||||
uploadedPhotos.add(photo);
|
||||
}
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
new Object() {
|
||||
public final int nombrePhotos = uploadedPhotos.size();
|
||||
public final List<Document> photos = uploadedPhotos;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE VISUALISATION ===
|
||||
|
||||
@GET
|
||||
@Path("/{id}/thumbnail")
|
||||
@Produces("image/*")
|
||||
@Operation(
|
||||
summary = "Miniature d'une photo",
|
||||
description = "Récupère une version miniature de la photo")
|
||||
@APIResponse(responseCode = "200", description = "Miniature récupérée")
|
||||
@APIResponse(responseCode = "404", description = "Photo non trouvée")
|
||||
public Response getThumbnail(
|
||||
@Parameter(description = "Identifiant de la photo", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Récupération de la miniature pour la photo: {}", id);
|
||||
|
||||
Document photo = documentService.findByIdRequired(id);
|
||||
|
||||
if (photo.getTypeDocument() != TypeDocument.PHOTO_CHANTIER) {
|
||||
throw new BadRequestException("Ce document n'est pas une photo");
|
||||
}
|
||||
|
||||
// Génération de miniature (simulation - en production utiliser une bibliothèque comme
|
||||
// Thumbnailator)
|
||||
InputStream inputStream =
|
||||
generateThumbnail(documentService.downloadDocument(id), photo.getTypeMime());
|
||||
|
||||
StreamingOutput streamingOutput =
|
||||
output -> {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
inputStream.close();
|
||||
};
|
||||
|
||||
return Response.ok(streamingOutput)
|
||||
.header("Content-Type", photo.getTypeMime())
|
||||
.header("Cache-Control", "public, max-age=3600")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/view")
|
||||
@Produces("image/*")
|
||||
@Operation(summary = "Visualiser une photo", description = "Affiche la photo en taille originale")
|
||||
@APIResponse(responseCode = "200", description = "Photo affichée")
|
||||
@APIResponse(responseCode = "404", description = "Photo non trouvée")
|
||||
public Response viewPhoto(
|
||||
@Parameter(description = "Identifiant de la photo", required = true) @PathParam("id")
|
||||
UUID id) {
|
||||
|
||||
logger.debug("Visualisation de la photo: {}", id);
|
||||
|
||||
Document photo = documentService.findByIdRequired(id);
|
||||
|
||||
if (photo.getTypeDocument() != TypeDocument.PHOTO_CHANTIER) {
|
||||
throw new BadRequestException("Ce document n'est pas une photo");
|
||||
}
|
||||
|
||||
InputStream inputStream = documentService.downloadDocument(id);
|
||||
|
||||
StreamingOutput streamingOutput =
|
||||
output -> {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
inputStream.close();
|
||||
};
|
||||
|
||||
return Response.ok(streamingOutput)
|
||||
.header("Content-Type", photo.getTypeMime())
|
||||
.header("Content-Disposition", "inline; filename=\"" + photo.getNomFichier() + "\"")
|
||||
.header("Cache-Control", "public, max-age=3600")
|
||||
.build();
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(
|
||||
summary = "Statistiques des photos",
|
||||
description = "Récupère les statistiques spécifiques aux photos")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées")
|
||||
public Response getStatistiquesPhotos() {
|
||||
logger.debug("Récupération des statistiques des photos");
|
||||
|
||||
List<Document> photos = documentService.findByType(TypeDocument.PHOTO_CHANTIER);
|
||||
|
||||
final long totalPhotosCount = photos.size();
|
||||
final long tailleTotalBytes = photos.stream().mapToLong(Document::getTailleFichier).sum();
|
||||
|
||||
// Statistiques par chantier
|
||||
final long chantiersAvecPhotosCount =
|
||||
photos.stream()
|
||||
.filter(p -> p.getChantier() != null)
|
||||
.map(p -> p.getChantier().getId())
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
final double tailleMoyenneCalc =
|
||||
totalPhotosCount > 0 ? (double) tailleTotalBytes / totalPhotosCount : 0;
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final long totalPhotos = totalPhotosCount;
|
||||
public final String tailleTotale = formatFileSize(tailleTotalBytes);
|
||||
public final long chantiersAvecPhotos = chantiersAvecPhotosCount;
|
||||
public final double tailleMoyenne = tailleMoyenneCalc;
|
||||
public final String tailleMoyenneFormatee = formatFileSize((long) tailleMoyenneCalc);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/galerie/{chantierId}")
|
||||
@Operation(
|
||||
summary = "Galerie photos d'un chantier",
|
||||
description = "Récupère toutes les photos d'un chantier pour affichage galerie")
|
||||
@APIResponse(responseCode = "200", description = "Galerie récupérée")
|
||||
public Response getGalerieChantier(
|
||||
@Parameter(description = "Identifiant du chantier", required = true) @PathParam("chantierId")
|
||||
UUID chantierId) {
|
||||
|
||||
logger.debug("Récupération de la galerie pour le chantier: {}", chantierId);
|
||||
|
||||
List<Document> photos =
|
||||
documentService.findByChantier(chantierId).stream()
|
||||
.filter(doc -> doc.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Informations de galerie enrichies
|
||||
final UUID chantierIdFinal = chantierId;
|
||||
final int nombrePhotosTotal = photos.size();
|
||||
final List<Object> photosEnrichies =
|
||||
photos.stream()
|
||||
.map(
|
||||
doc ->
|
||||
new Object() {
|
||||
public final UUID id = doc.getId();
|
||||
public final String nom = doc.getNom();
|
||||
public final String description = doc.getDescription();
|
||||
public final String tailleFormatee = doc.getTailleFormatee();
|
||||
public final LocalDateTime dateCreation = doc.getDateCreation();
|
||||
public final String tags = doc.getTags();
|
||||
public final String urlThumbnail = "/photos/" + doc.getId() + "/thumbnail";
|
||||
public final String urlView = "/photos/" + doc.getId() + "/view";
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(
|
||||
new Object() {
|
||||
public final UUID chantierId = chantierIdFinal;
|
||||
public final int nombrePhotos = nombrePhotosTotal;
|
||||
public final List<Object> photos = photosEnrichies;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES ===
|
||||
|
||||
private void validatePhotoUpload(FileUpload file, String fileName, String contentType) {
|
||||
if (file == null) {
|
||||
throw new BadRequestException("Aucun fichier fourni");
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.trim().isEmpty()) {
|
||||
throw new BadRequestException("Nom de fichier manquant");
|
||||
}
|
||||
|
||||
if (contentType == null || !isValidImageType(contentType)) {
|
||||
throw new BadRequestException("Type de fichier non supporté pour les photos: " + contentType);
|
||||
}
|
||||
|
||||
if (file.size() > MAX_PHOTO_SIZE) {
|
||||
throw new BadRequestException(
|
||||
"Photo trop volumineuse (max: " + formatFileSize(MAX_PHOTO_SIZE) + ")");
|
||||
}
|
||||
|
||||
// Validation supplémentaire si nécessaire
|
||||
// Le chantierId peut être null dans certains cas
|
||||
}
|
||||
|
||||
private boolean isValidImageType(String contentType) {
|
||||
if (contentType == null) return false;
|
||||
return Arrays.stream(ALLOWED_IMAGE_TYPES).anyMatch(type -> type.equalsIgnoreCase(contentType));
|
||||
}
|
||||
|
||||
private String formatFileSize(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
|
||||
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
|
||||
return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une miniature pour une image Simulation - en production, utiliser une bibliothèque comme
|
||||
* Thumbnailator ou ImageIO
|
||||
*/
|
||||
private InputStream generateThumbnail(InputStream originalStream, String mimeType) {
|
||||
try {
|
||||
// Simulation simple - en production, implémenter une vraie génération de miniatures
|
||||
// Utiliser des bibliothèques comme :
|
||||
// - Thumbnailator: Thumbnails.of(originalStream).size(200,
|
||||
// 200).outputFormat("jpg").toOutputStream()
|
||||
// - ImageIO avec BufferedImage
|
||||
// - Apache Commons Imaging
|
||||
|
||||
logger.debug("Génération de miniature (simulée) pour type MIME: {}", mimeType);
|
||||
|
||||
// Pour la simulation, retourner le stream original
|
||||
// En production, générer une vraie miniature de 200x200 pixels
|
||||
return originalStream;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération de miniature: {}", e.getMessage());
|
||||
// En cas d'erreur, retourner l'image originale
|
||||
return originalStream;
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES DE REQUÊTE ===
|
||||
|
||||
public static class UploadPhotoForm {
|
||||
@RestForm("nom")
|
||||
@Schema(description = "Nom de la photo")
|
||||
public String nom;
|
||||
|
||||
@RestForm("description")
|
||||
@Schema(description = "Description de la photo")
|
||||
public String description;
|
||||
|
||||
@RestForm("file")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Schema(description = "Fichier image à uploader", required = true)
|
||||
public InputStream file;
|
||||
|
||||
@RestForm("fileName")
|
||||
@Schema(description = "Nom du fichier image", required = true)
|
||||
public String fileName;
|
||||
|
||||
@RestForm("contentType")
|
||||
@Schema(description = "Type MIME de l'image", required = true)
|
||||
public String contentType;
|
||||
|
||||
@RestForm("fileSize")
|
||||
@Schema(description = "Taille du fichier en bytes", required = true)
|
||||
public long fileSize;
|
||||
|
||||
@RestForm("chantierId")
|
||||
@Schema(description = "ID du chantier", required = true)
|
||||
public UUID chantierId;
|
||||
|
||||
@RestForm("employeId")
|
||||
@Schema(description = "ID de l'employé qui prend la photo")
|
||||
public UUID employeId;
|
||||
|
||||
@RestForm("tags")
|
||||
@Schema(description = "Tags descriptifs (ex: 'avancement,façade,jour1')")
|
||||
public String tags;
|
||||
|
||||
@RestForm("estPublic")
|
||||
@Schema(description = "Photo visible publiquement")
|
||||
public Boolean estPublic;
|
||||
|
||||
@RestForm("userId")
|
||||
@Schema(description = "ID de l'utilisateur qui upload")
|
||||
public UUID userId;
|
||||
}
|
||||
|
||||
public static class FileUploadInfo {
|
||||
public InputStream file;
|
||||
public String fileName;
|
||||
public String contentType;
|
||||
public long fileSize;
|
||||
|
||||
public FileUploadInfo(InputStream file, String fileName, String contentType, long fileSize) {
|
||||
this.file = file;
|
||||
this.fileName = fileName;
|
||||
this.contentType = contentType;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UploadMultiplePhotosForm {
|
||||
@RestForm("files")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Schema(description = "Fichiers images à uploader")
|
||||
public List<InputStream> files;
|
||||
|
||||
@RestForm("fileNames")
|
||||
@Schema(description = "Noms des fichiers")
|
||||
public List<String> fileNames;
|
||||
|
||||
@RestForm("contentTypes")
|
||||
@Schema(description = "Types MIME des fichiers")
|
||||
public List<String> contentTypes;
|
||||
|
||||
@RestForm("fileSizes")
|
||||
@Schema(description = "Tailles des fichiers")
|
||||
public List<Long> fileSizes;
|
||||
|
||||
@RestForm("nomBase")
|
||||
@Schema(description = "Nom de base pour les photos")
|
||||
public String nomBase;
|
||||
|
||||
@RestForm("description")
|
||||
@Schema(description = "Description commune aux photos")
|
||||
public String description;
|
||||
|
||||
@RestForm("chantierId")
|
||||
@Schema(description = "ID du chantier", required = true)
|
||||
public UUID chantierId;
|
||||
|
||||
@RestForm("employeId")
|
||||
@Schema(description = "ID de l'employé")
|
||||
public UUID employeId;
|
||||
|
||||
@RestForm("tags")
|
||||
@Schema(description = "Tags communs aux photos")
|
||||
public String tags;
|
||||
|
||||
@RestForm("estPublic")
|
||||
@Schema(description = "Photos visibles publiquement")
|
||||
public Boolean estPublic;
|
||||
|
||||
@RestForm("userId")
|
||||
@Schema(description = "ID de l'utilisateur qui upload")
|
||||
public UUID userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.PlanningService;
|
||||
import dev.lions.btpxpress.domain.core.entity.PlanningEvent;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypePlanningEvent;
|
||||
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.time.LocalDateTime;
|
||||
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 du planning - Architecture 2025 MÉTIER: Gestion complète planning
|
||||
* BTP avec détection conflits
|
||||
*/
|
||||
@Path("/api/v1/planning")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Planning", description = "Gestion du planning et des événements BTP")
|
||||
public class PlanningResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PlanningResource.class);
|
||||
|
||||
@Inject PlanningService planningService;
|
||||
|
||||
// === ENDPOINTS VUE PLANNING GÉNÉRAL ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer la vue planning général")
|
||||
@APIResponse(responseCode = "200", description = "Planning général récupéré avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de date invalides")
|
||||
public Response getPlanningGeneral(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin,
|
||||
@Parameter(description = "ID du chantier (optionnel)") @QueryParam("chantierId")
|
||||
String chantierId,
|
||||
@Parameter(description = "ID de l'équipe (optionnel)") @QueryParam("equipeId")
|
||||
String equipeId,
|
||||
@Parameter(description = "Type d'événement (optionnel)") @QueryParam("type") String type) {
|
||||
try {
|
||||
LocalDate debut = dateDebut != null ? LocalDate.parse(dateDebut) : LocalDate.now();
|
||||
LocalDate fin = dateFin != null ? LocalDate.parse(dateFin) : debut.plusDays(30);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
UUID chantierUUID = chantierId != null ? UUID.fromString(chantierId) : null;
|
||||
UUID equipeUUID = equipeId != null ? UUID.fromString(equipeId) : null;
|
||||
TypePlanningEvent typeEvent =
|
||||
type != null ? TypePlanningEvent.valueOf(type.toUpperCase()) : null;
|
||||
|
||||
Object planning =
|
||||
planningService.getPlanningGeneral(debut, fin, chantierUUID, equipeUUID, typeEvent);
|
||||
|
||||
return Response.ok(planning).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Paramètres invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du planning général", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/week/{date}")
|
||||
@Operation(summary = "Récupérer le planning hebdomadaire")
|
||||
@APIResponse(responseCode = "200", description = "Planning hebdomadaire récupéré avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Date invalide")
|
||||
public Response getPlanningWeek(
|
||||
@Parameter(description = "Date de référence (YYYY-MM-DD)") @PathParam("date") String date) {
|
||||
try {
|
||||
LocalDate dateRef = LocalDate.parse(date);
|
||||
Object planningWeek = planningService.getPlanningWeek(dateRef);
|
||||
|
||||
return Response.ok(planningWeek).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("Date invalide: " + date).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du planning hebdomadaire", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/month/{date}")
|
||||
@Operation(summary = "Récupérer le planning mensuel")
|
||||
@APIResponse(responseCode = "200", description = "Planning mensuel récupéré avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Date invalide")
|
||||
public Response getPlanningMonth(
|
||||
@Parameter(description = "Date de référence (YYYY-MM-DD)") @PathParam("date") String date) {
|
||||
try {
|
||||
LocalDate dateRef = LocalDate.parse(date);
|
||||
Object planningMonth = planningService.getPlanningMonth(dateRef);
|
||||
|
||||
return Response.ok(planningMonth).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("Date invalide: " + date).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du planning mensuel", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération du planning: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS GESTION ÉVÉNEMENTS ===
|
||||
|
||||
@GET
|
||||
@Path("/events")
|
||||
@Operation(summary = "Récupérer tous les événements de planning")
|
||||
@APIResponse(responseCode = "200", description = "Liste des événements récupérée avec succès")
|
||||
public Response getAllEvents(
|
||||
@Parameter(description = "Date de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin (YYYY-MM-DD)") @QueryParam("dateFin") String dateFin,
|
||||
@Parameter(description = "Type d'événement") @QueryParam("type") String type,
|
||||
@Parameter(description = "ID du chantier") @QueryParam("chantierId") String chantierId) {
|
||||
try {
|
||||
List<PlanningEvent> events;
|
||||
|
||||
if (dateDebut != null && dateFin != null) {
|
||||
LocalDate debut = LocalDate.parse(dateDebut);
|
||||
LocalDate fin = LocalDate.parse(dateFin);
|
||||
events = planningService.findEventsByDateRange(debut, fin);
|
||||
} else if (type != null) {
|
||||
TypePlanningEvent typeEvent = TypePlanningEvent.valueOf(type.toUpperCase());
|
||||
events = planningService.findEventsByType(typeEvent);
|
||||
} else if (chantierId != null) {
|
||||
UUID chantierUUID = UUID.fromString(chantierId);
|
||||
events = planningService.findEventsByChantier(chantierUUID);
|
||||
} else {
|
||||
events = planningService.findAllEvents();
|
||||
}
|
||||
|
||||
return Response.ok(events).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Paramètres invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des événements", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des événements: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/events/{id}")
|
||||
@Operation(summary = "Récupérer un événement par ID")
|
||||
@APIResponse(responseCode = "200", description = "Événement récupéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
public Response getEventById(
|
||||
@Parameter(description = "ID de l'événement") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID eventId = UUID.fromString(id);
|
||||
return planningService
|
||||
.findEventById(eventId)
|
||||
.map(event -> Response.ok(event).build())
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Événement non trouvé avec l'ID: " + id)
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID d'événement invalide: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération de l'événement {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de l'événement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/events")
|
||||
@Operation(summary = "Créer un nouvel événement de planning")
|
||||
@APIResponse(responseCode = "201", description = "Événement créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Conflit de ressources détecté")
|
||||
public Response createEvent(
|
||||
@Parameter(description = "Données du nouvel événement") @Valid @NotNull
|
||||
CreateEventRequest request) {
|
||||
try {
|
||||
PlanningEvent event =
|
||||
planningService.createEvent(
|
||||
request.titre,
|
||||
request.description,
|
||||
request.type,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.chantierId,
|
||||
request.equipeId,
|
||||
request.employeIds,
|
||||
request.materielIds);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(event).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("Conflit de ressources: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création de l'événement", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de l'événement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/events/{id}")
|
||||
@Operation(summary = "Modifier un événement de planning")
|
||||
@APIResponse(responseCode = "200", description = "Événement modifié avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
@APIResponse(responseCode = "409", description = "Conflit de ressources détecté")
|
||||
public Response updateEvent(
|
||||
@Parameter(description = "ID de l'événement") @PathParam("id") String id,
|
||||
@Parameter(description = "Nouvelles données de l'événement") @Valid @NotNull
|
||||
UpdateEventRequest request) {
|
||||
try {
|
||||
UUID eventId = UUID.fromString(id);
|
||||
PlanningEvent event =
|
||||
planningService.updateEvent(
|
||||
eventId,
|
||||
request.titre,
|
||||
request.description,
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.equipeId,
|
||||
request.employeIds,
|
||||
request.materielIds);
|
||||
|
||||
return Response.ok(event).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("Conflit de ressources: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la modification de l'événement {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification de l'événement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/events/{id}")
|
||||
@Operation(summary = "Supprimer un événement de planning")
|
||||
@APIResponse(responseCode = "204", description = "Événement supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
public Response deleteEvent(
|
||||
@Parameter(description = "ID de l'événement") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID eventId = UUID.fromString(id);
|
||||
planningService.deleteEvent(eventId);
|
||||
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID invalide: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression de l'événement {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de l'événement: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DÉTECTION CONFLITS ===
|
||||
|
||||
@GET
|
||||
@Path("/conflicts")
|
||||
@Operation(summary = "Détecter les conflits de ressources")
|
||||
@APIResponse(responseCode = "200", description = "Conflits détectés avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
public Response detectConflicts(
|
||||
@Parameter(description = "Date de début pour la vérification") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Date de fin pour la vérification") @QueryParam("dateFin")
|
||||
String dateFin,
|
||||
@Parameter(description = "Type de ressource (EMPLOYE, MATERIEL, EQUIPE)")
|
||||
@QueryParam("resourceType")
|
||||
String resourceType) {
|
||||
try {
|
||||
LocalDate debut = dateDebut != null ? LocalDate.parse(dateDebut) : LocalDate.now();
|
||||
LocalDate fin = dateFin != null ? LocalDate.parse(dateFin) : debut.plusDays(7);
|
||||
|
||||
List<Object> conflicts = planningService.detectConflicts(debut, fin, resourceType);
|
||||
|
||||
return Response.ok(new ConflictsResponse(conflicts)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Paramètres invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la détection des conflits", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la détection des conflits: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/check-availability")
|
||||
@Operation(summary = "Vérifier la disponibilité des ressources")
|
||||
@APIResponse(responseCode = "200", description = "Disponibilité vérifiée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response checkAvailability(
|
||||
@Parameter(description = "Critères de vérification de disponibilité") @Valid @NotNull
|
||||
AvailabilityCheckRequest request) {
|
||||
try {
|
||||
boolean available =
|
||||
planningService.checkResourcesAvailability(
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.employeIds,
|
||||
request.materielIds,
|
||||
request.equipeId);
|
||||
|
||||
Object details =
|
||||
planningService.getAvailabilityDetails(
|
||||
request.dateDebut,
|
||||
request.dateFin,
|
||||
request.employeIds,
|
||||
request.materielIds,
|
||||
request.equipeId);
|
||||
|
||||
return Response.ok(new AvailabilityResponse(available, details)).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 vérification de disponibilité", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la vérification: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS STATISTIQUES ===
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques du planning")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
public Response getPlanningStats(
|
||||
@Parameter(description = "Période de début (YYYY-MM-DD)") @QueryParam("dateDebut")
|
||||
String dateDebut,
|
||||
@Parameter(description = "Période de fin (YYYY-MM-DD)") @QueryParam("dateFin")
|
||||
String dateFin) {
|
||||
try {
|
||||
LocalDate debut =
|
||||
dateDebut != null ? LocalDate.parse(dateDebut) : LocalDate.now().minusDays(30);
|
||||
LocalDate fin = dateFin != null ? LocalDate.parse(dateFin) : LocalDate.now();
|
||||
|
||||
Object stats = planningService.getStatistics(debut, fin);
|
||||
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la génération des statistiques planning", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === CLASSES UTILITAIRES ===
|
||||
|
||||
public static record CreateEventRequest(
|
||||
@Parameter(description = "Titre de l'événement") String titre,
|
||||
@Parameter(description = "Description de l'événement") String description,
|
||||
@Parameter(description = "Type d'événement") String type,
|
||||
@Parameter(description = "Date et heure de début") LocalDateTime dateDebut,
|
||||
@Parameter(description = "Date et heure de fin") LocalDateTime dateFin,
|
||||
@Parameter(description = "ID du chantier concerné") UUID chantierId,
|
||||
@Parameter(description = "ID de l'équipe assignée") UUID equipeId,
|
||||
@Parameter(description = "Liste des IDs des employés") List<UUID> employeIds,
|
||||
@Parameter(description = "Liste des IDs du matériel") List<UUID> materielIds) {}
|
||||
|
||||
public static record UpdateEventRequest(
|
||||
@Parameter(description = "Nouveau titre") String titre,
|
||||
@Parameter(description = "Nouvelle description") String description,
|
||||
@Parameter(description = "Nouvelle date de début") LocalDateTime dateDebut,
|
||||
@Parameter(description = "Nouvelle date de fin") LocalDateTime dateFin,
|
||||
@Parameter(description = "Nouvel ID d'équipe") UUID equipeId,
|
||||
@Parameter(description = "Nouveaux IDs des employés") List<UUID> employeIds,
|
||||
@Parameter(description = "Nouveaux IDs du matériel") List<UUID> materielIds) {}
|
||||
|
||||
public static record AvailabilityCheckRequest(
|
||||
@Parameter(description = "Date de début") LocalDateTime dateDebut,
|
||||
@Parameter(description = "Date de fin") LocalDateTime dateFin,
|
||||
@Parameter(description = "IDs des employés à vérifier") List<UUID> employeIds,
|
||||
@Parameter(description = "IDs du matériel à vérifier") List<UUID> materielIds,
|
||||
@Parameter(description = "ID de l'équipe à vérifier") UUID equipeId) {}
|
||||
|
||||
public static record ConflictsResponse(List<Object> conflicts) {}
|
||||
|
||||
public static record AvailabilityResponse(boolean available, Object details) {}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.*;
|
||||
import dev.lions.btpxpress.domain.core.entity.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
import java.io.PrintWriter;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
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 les rapports et exports - Architecture 2025 REPORTING: API de génération de
|
||||
* rapports BTP avec exports
|
||||
*/
|
||||
@Path("/api/v1/reports")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Reports", description = "Génération de rapports et exports BTP")
|
||||
public class ReportResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReportResource.class);
|
||||
|
||||
@Inject ChantierService chantierService;
|
||||
|
||||
@Inject EquipeService equipeService;
|
||||
|
||||
@Inject EmployeService employeService;
|
||||
|
||||
@Inject MaterielService materielService;
|
||||
|
||||
@Inject MaintenanceService maintenanceService;
|
||||
|
||||
@Inject DocumentService documentService;
|
||||
|
||||
@Inject DisponibiliteService disponibiliteService;
|
||||
|
||||
@Inject PlanningService planningService;
|
||||
|
||||
// === RAPPORTS DE CHANTIERS ===
|
||||
|
||||
@GET
|
||||
@Path("/chantiers")
|
||||
@Operation(
|
||||
summary = "Rapport des chantiers",
|
||||
description = "Génère un rapport détaillé des chantiers avec filtres")
|
||||
@APIResponse(responseCode = "200", description = "Rapport généré avec succès")
|
||||
public Response getRapportChantiers(
|
||||
@Parameter(description = "Date de début (yyyy-mm-dd)") @QueryParam("dateDebut")
|
||||
String dateDebutStr,
|
||||
@Parameter(description = "Date de fin (yyyy-mm-dd)") @QueryParam("dateFin") String dateFinStr,
|
||||
@Parameter(description = "Statut des chantiers") @QueryParam("statut") String statutStr,
|
||||
@Parameter(description = "Format d'export", example = "json")
|
||||
@QueryParam("format")
|
||||
@DefaultValue("json")
|
||||
String format) {
|
||||
|
||||
logger.info("Génération du rapport chantiers - format: {}", format);
|
||||
|
||||
LocalDate dateDebut = parseDate(dateDebutStr, LocalDate.now().minusMonths(1));
|
||||
LocalDate dateFin = parseDate(dateFinStr, LocalDate.now());
|
||||
StatutChantier statut = parseStatutChantier(statutStr);
|
||||
|
||||
List<Chantier> chantiers;
|
||||
if (statut != null) {
|
||||
chantiers = chantierService.findByStatut(statut);
|
||||
} else {
|
||||
chantiers = chantierService.findByDateRange(dateDebut, dateFin);
|
||||
}
|
||||
|
||||
// Variables locales pour éviter les self-references
|
||||
final LocalDate dateDebutRef = dateDebut;
|
||||
final LocalDate dateFinRef = dateFin;
|
||||
final String statutRef = statutStr;
|
||||
|
||||
// Enrichissement des données pour le rapport
|
||||
final List<Object> chantiersEnrichis =
|
||||
chantiers.stream()
|
||||
.map(
|
||||
chantier ->
|
||||
new Object() {
|
||||
public final UUID id = chantier.getId();
|
||||
public final String nom = chantier.getNom();
|
||||
public final String description = chantier.getDescription();
|
||||
public final String adresse = chantier.getAdresse();
|
||||
public final String statut = chantier.getStatut().toString();
|
||||
public final LocalDate dateDebut = chantier.getDateDebut();
|
||||
public final LocalDate dateFinPrevue = chantier.getDateFinPrevue();
|
||||
public final LocalDate dateFinReelle = chantier.getDateFinReelle();
|
||||
public final double montant =
|
||||
chantier.getMontantPrevu() != null
|
||||
? chantier.getMontantPrevu().doubleValue()
|
||||
: 0.0;
|
||||
public final String client =
|
||||
chantier.getClient() != null
|
||||
? chantier.getClient().getNom()
|
||||
: "Non défini";
|
||||
public final long nombreDocuments =
|
||||
documentService.findByChantier(chantier.getId()).size();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final int nombreChantiersRef = chantiersEnrichis.size();
|
||||
final double budgetTotalRef =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getMontantPrevu() != null)
|
||||
.mapToDouble(c -> c.getMontantPrevu().doubleValue())
|
||||
.sum();
|
||||
|
||||
Object rapport =
|
||||
new Object() {
|
||||
public final String titre = "Rapport des Chantiers";
|
||||
public final LocalDate dateDebut = dateDebutRef;
|
||||
public final LocalDate dateFin = dateFinRef;
|
||||
public final String statut = statutRef != null ? statutRef : "Tous";
|
||||
public final int nombreChantiers = nombreChantiersRef;
|
||||
public final double budgetTotal = budgetTotalRef;
|
||||
public final List<Object> chantiers = chantiersEnrichis;
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
};
|
||||
|
||||
return handleFormatResponse(rapport, format, "rapport_chantiers");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chantiers/{id}/detail")
|
||||
@Operation(
|
||||
summary = "Rapport détaillé d'un chantier",
|
||||
description = "Génère un rapport complet pour un chantier spécifique")
|
||||
@APIResponse(responseCode = "200", description = "Rapport de chantier généré")
|
||||
@APIResponse(responseCode = "404", description = "Chantier non trouvé")
|
||||
public Response getRapportChantierDetail(
|
||||
@Parameter(description = "Identifiant du chantier", required = true) @PathParam("id")
|
||||
UUID chantierId,
|
||||
@Parameter(description = "Format d'export", example = "json")
|
||||
@QueryParam("format")
|
||||
@DefaultValue("json")
|
||||
String format) {
|
||||
|
||||
logger.info("Génération du rapport détaillé pour le chantier: {}", chantierId);
|
||||
|
||||
Chantier chantierEntity =
|
||||
chantierService
|
||||
.findById(chantierId)
|
||||
.orElseThrow(() -> new NotFoundException("Chantier non trouvé: " + chantierId));
|
||||
List<Document> documents = documentService.findByChantier(chantierId);
|
||||
|
||||
Object rapportDetail =
|
||||
new Object() {
|
||||
public final String titre = "Rapport Détaillé du Chantier";
|
||||
public final Object chantier =
|
||||
new Object() {
|
||||
public final UUID id = chantierEntity.getId();
|
||||
public final String nom = chantierEntity.getNom();
|
||||
public final String description = chantierEntity.getDescription();
|
||||
public final String adresse = chantierEntity.getAdresse();
|
||||
public final String statut = chantierEntity.getStatut().toString();
|
||||
public final LocalDate dateDebut = chantierEntity.getDateDebut();
|
||||
public final LocalDate dateFinPrevue = chantierEntity.getDateFinPrevue();
|
||||
public final LocalDate dateFinReelle = chantierEntity.getDateFinReelle();
|
||||
public final double montant =
|
||||
chantierEntity.getMontantPrevu() != null
|
||||
? chantierEntity.getMontantPrevu().doubleValue()
|
||||
: 0.0;
|
||||
public final String client =
|
||||
chantierEntity.getClient() != null
|
||||
? chantierEntity.getClient().getNom()
|
||||
+ " ("
|
||||
+ chantierEntity.getClient().getEmail()
|
||||
+ ")"
|
||||
: "Non défini";
|
||||
};
|
||||
public final Object statistiques =
|
||||
new Object() {
|
||||
public final int nombreDocuments = documents.size();
|
||||
public final long tailleDocuments =
|
||||
documents.stream().mapToLong(Document::getTailleFichier).sum();
|
||||
public final long nombrePhotos =
|
||||
documents.stream()
|
||||
.filter(d -> d.getTypeDocument() == TypeDocument.PHOTO_CHANTIER)
|
||||
.count();
|
||||
public final long nombrePlans =
|
||||
documents.stream()
|
||||
.filter(d -> d.getTypeDocument() == TypeDocument.PLAN)
|
||||
.count();
|
||||
};
|
||||
public final List<Object> documentsRecents =
|
||||
documents.stream()
|
||||
.limit(10)
|
||||
.map(
|
||||
doc ->
|
||||
new Object() {
|
||||
public final String nom = doc.getNom();
|
||||
public final String type = doc.getTypeDocument().toString();
|
||||
public final LocalDateTime dateCreation = doc.getDateCreation();
|
||||
public final String taille = doc.getTailleFormatee();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
};
|
||||
|
||||
return handleFormatResponse(rapportDetail, format, "rapport_chantier_" + chantierId);
|
||||
}
|
||||
|
||||
// === RAPPORTS DE MAINTENANCE ===
|
||||
|
||||
@GET
|
||||
@Path("/maintenance")
|
||||
@Operation(
|
||||
summary = "Rapport de maintenance",
|
||||
description = "Génère un rapport sur l'état de la maintenance du matériel")
|
||||
@APIResponse(responseCode = "200", description = "Rapport de maintenance généré")
|
||||
public Response getRapportMaintenance(
|
||||
@Parameter(description = "Nombre de jours pour l'historique", example = "30")
|
||||
@QueryParam("periode")
|
||||
@DefaultValue("30")
|
||||
int periodeJours,
|
||||
@Parameter(description = "Format d'export", example = "json")
|
||||
@QueryParam("format")
|
||||
@DefaultValue("json")
|
||||
String format) {
|
||||
|
||||
logger.info("Génération du rapport de maintenance - période: {} jours", periodeJours);
|
||||
|
||||
List<MaintenanceMateriel> maintenancesEnRetard = maintenanceService.findEnRetard();
|
||||
List<MaintenanceMateriel> prochainesMaintenances =
|
||||
maintenanceService.findProchainesMaintenances(30);
|
||||
List<MaintenanceMateriel> maintenancesRecentes =
|
||||
maintenanceService.findTerminees().stream().limit(50).collect(Collectors.toList());
|
||||
|
||||
final int periodeJoursRef = periodeJours;
|
||||
final int maintenancesEnRetardCount = maintenancesEnRetard.size();
|
||||
final int prochainesMaintenancesCount = prochainesMaintenances.size();
|
||||
final int maintenancesRecentesCount = maintenancesRecentes.size();
|
||||
|
||||
Object rapport =
|
||||
new Object() {
|
||||
public final String titre = "Rapport de Maintenance";
|
||||
public final int periodeJours = periodeJoursRef;
|
||||
public final Object resume =
|
||||
new Object() {
|
||||
public final int maintenancesEnRetard = maintenancesEnRetardCount;
|
||||
public final int prochainesMaintenances = prochainesMaintenancesCount;
|
||||
public final int maintenancesRecentesTerminees = maintenancesRecentesCount;
|
||||
public final boolean alerteCritique = maintenancesEnRetardCount > 0;
|
||||
};
|
||||
public final List<Object> enRetard =
|
||||
maintenancesEnRetard.stream()
|
||||
.map(
|
||||
m ->
|
||||
new Object() {
|
||||
public final String materiel = m.getMateriel().getNom();
|
||||
public final String type = m.getType().toString();
|
||||
public final LocalDate datePrevue = m.getDatePrevue();
|
||||
public final long joursRetard =
|
||||
LocalDate.now().toEpochDay() - m.getDatePrevue().toEpochDay();
|
||||
public final String technicien = m.getTechnicien();
|
||||
public final String description = m.getDescription();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final List<Object> aVenir =
|
||||
prochainesMaintenances.stream()
|
||||
.map(
|
||||
m ->
|
||||
new Object() {
|
||||
public final String materiel = m.getMateriel().getNom();
|
||||
public final String type = m.getType().toString();
|
||||
public final LocalDate datePrevue = m.getDatePrevue();
|
||||
public final long joursDici =
|
||||
m.getDatePrevue().toEpochDay() - LocalDate.now().toEpochDay();
|
||||
public final String technicien = m.getTechnicien();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final List<Object> terminees =
|
||||
maintenancesRecentes.stream()
|
||||
.map(
|
||||
m ->
|
||||
new Object() {
|
||||
public final String materiel = m.getMateriel().getNom();
|
||||
public final String type = m.getType().toString();
|
||||
public final LocalDate dateRealisee = m.getDateRealisee();
|
||||
public final String technicien = m.getTechnicien();
|
||||
public final String statut = m.getStatut().toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
};
|
||||
|
||||
return handleFormatResponse(rapport, format, "rapport_maintenance");
|
||||
}
|
||||
|
||||
// === RAPPORTS DE RESSOURCES HUMAINES ===
|
||||
|
||||
@GET
|
||||
@Path("/ressources-humaines")
|
||||
@Operation(
|
||||
summary = "Rapport des ressources humaines",
|
||||
description = "Rapport sur les employés, équipes et disponibilités")
|
||||
@APIResponse(responseCode = "200", description = "Rapport RH généré")
|
||||
public Response getRapportRH(
|
||||
@Parameter(description = "Format d'export", example = "json")
|
||||
@QueryParam("format")
|
||||
@DefaultValue("json")
|
||||
String format) {
|
||||
|
||||
logger.info("Génération du rapport des ressources humaines");
|
||||
|
||||
List<Employe> employes = employeService.findAll();
|
||||
List<Equipe> equipes = equipeService.findAll();
|
||||
List<Disponibilite> disponibilitesEnAttente = disponibiliteService.findEnAttente();
|
||||
List<Disponibilite> disponibilitesActuelles = disponibiliteService.findActuelles();
|
||||
|
||||
final int totalEmployesCount = employes.size();
|
||||
final int totalEquipesCount = equipes.size();
|
||||
final int disponibilitesEnAttenteCount = disponibilitesEnAttente.size();
|
||||
final int disponibilitesActuellesCount = disponibilitesActuelles.size();
|
||||
|
||||
Object rapport =
|
||||
new Object() {
|
||||
public final String titre = "Rapport des Ressources Humaines";
|
||||
public final Object resume =
|
||||
new Object() {
|
||||
public final int totalEmployes = totalEmployesCount;
|
||||
public final int totalEquipes = totalEquipesCount;
|
||||
public final int disponibilitesEnAttente = disponibilitesEnAttenteCount;
|
||||
public final int disponibilitesActuelles = disponibilitesActuellesCount;
|
||||
};
|
||||
public final List<Object> equipesDetail =
|
||||
equipes.stream()
|
||||
.map(
|
||||
equipeEntity ->
|
||||
new Object() {
|
||||
public final String nom = equipeEntity.getNom();
|
||||
public final String specialites =
|
||||
equipeEntity.getSpecialites() != null
|
||||
? String.join(", ", equipeEntity.getSpecialites())
|
||||
: "Non défini";
|
||||
public final String statut = equipeEntity.getStatut().toString();
|
||||
public final int nombreMembres =
|
||||
employes.stream()
|
||||
.filter(
|
||||
emp ->
|
||||
equipeEntity
|
||||
.getId()
|
||||
.equals(
|
||||
emp.getEquipe() != null
|
||||
? emp.getEquipe().getId()
|
||||
: null))
|
||||
.mapToInt(emp -> 1)
|
||||
.sum();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final List<Object> disponibilitesEnCours =
|
||||
disponibilitesEnAttente.stream()
|
||||
.map(
|
||||
dispo ->
|
||||
new Object() {
|
||||
public final String employe =
|
||||
dispo.getEmploye().getNom() + " " + dispo.getEmploye().getPrenom();
|
||||
public final String type = dispo.getType().toString();
|
||||
public final LocalDateTime dateDebut = dispo.getDateDebut();
|
||||
public final LocalDateTime dateFin = dispo.getDateFin();
|
||||
public final String motif = dispo.getMotif();
|
||||
public final String statut = "EN_ATTENTE";
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
};
|
||||
|
||||
return handleFormatResponse(rapport, format, "rapport_rh");
|
||||
}
|
||||
|
||||
// === RAPPORTS FINANCIERS ===
|
||||
|
||||
@GET
|
||||
@Path("/financier")
|
||||
@Operation(
|
||||
summary = "Rapport financier",
|
||||
description = "Rapport sur les budgets et coûts des chantiers")
|
||||
@APIResponse(responseCode = "200", description = "Rapport financier généré")
|
||||
public Response getRapportFinancier(
|
||||
@Parameter(description = "Année de référence", example = "2025") @QueryParam("annee")
|
||||
Integer annee,
|
||||
@Parameter(description = "Format d'export", example = "json")
|
||||
@QueryParam("format")
|
||||
@DefaultValue("json")
|
||||
String format) {
|
||||
|
||||
logger.info("Génération du rapport financier - année: {}", annee);
|
||||
|
||||
if (annee == null) {
|
||||
annee = LocalDate.now().getYear();
|
||||
}
|
||||
|
||||
// Filtrage par année des chantiers créés dans l'année
|
||||
LocalDate debutAnnee = LocalDate.of(annee, 1, 1);
|
||||
LocalDate finAnnee = LocalDate.of(annee, 12, 31);
|
||||
List<Chantier> chantiers = chantierService.findByDateRange(debutAnnee, finAnnee);
|
||||
|
||||
List<Chantier> chantiersTermines =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getStatut() == StatutChantier.TERMINE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final int anneeRef = annee;
|
||||
final int nombreChantiersCalc = chantiers.size();
|
||||
final int chantiersTerminesCalc = chantiersTermines.size();
|
||||
final double budgetTotalCalc =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getMontantPrevu() != null)
|
||||
.mapToDouble(c -> c.getMontantPrevu().doubleValue())
|
||||
.sum();
|
||||
final double budgetMoyenCalc = chantiers.size() > 0 ? budgetTotalCalc / chantiers.size() : 0;
|
||||
|
||||
Object rapport =
|
||||
new Object() {
|
||||
public final String titre = "Rapport Financier";
|
||||
public final int annee = anneeRef;
|
||||
public final Object resume =
|
||||
new Object() {
|
||||
public final int nombreChantiers = nombreChantiersCalc;
|
||||
public final int chantiersTermines = chantiersTerminesCalc;
|
||||
public final double budgetTotal = budgetTotalCalc;
|
||||
public final double budgetMoyen = budgetMoyenCalc;
|
||||
public final String budgetTotalFormate = formatMontant(budgetTotalCalc);
|
||||
};
|
||||
public final List<Object> chantiersParBudget =
|
||||
chantiers.stream()
|
||||
.sorted(
|
||||
(c1, c2) ->
|
||||
Double.compare(
|
||||
c2.getMontantPrevu() != null
|
||||
? c2.getMontantPrevu().doubleValue()
|
||||
: 0.0,
|
||||
c1.getMontantPrevu() != null
|
||||
? c1.getMontantPrevu().doubleValue()
|
||||
: 0.0))
|
||||
.limit(10)
|
||||
.map(
|
||||
chantier ->
|
||||
new Object() {
|
||||
public final String nom = chantier.getNom();
|
||||
public final double budget =
|
||||
chantier.getMontantPrevu() != null
|
||||
? chantier.getMontantPrevu().doubleValue()
|
||||
: 0.0;
|
||||
public final String budgetFormate =
|
||||
formatMontant(
|
||||
chantier.getMontantPrevu() != null
|
||||
? chantier.getMontantPrevu().doubleValue()
|
||||
: 0.0);
|
||||
public final String statut = chantier.getStatut().toString();
|
||||
public final String client =
|
||||
chantier.getClient() != null
|
||||
? chantier.getClient().getNom()
|
||||
: "Non défini";
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
public final Object repartitionParStatut =
|
||||
new Object() {
|
||||
public final long enCours =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getStatut() == StatutChantier.EN_COURS)
|
||||
.count();
|
||||
public final long termines =
|
||||
chantiers.stream().filter(c -> c.getStatut() == StatutChantier.TERMINE).count();
|
||||
public final long planifies =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getStatut() == StatutChantier.PLANIFIE)
|
||||
.count();
|
||||
public final long suspendus =
|
||||
chantiers.stream()
|
||||
.filter(c -> c.getStatut() == StatutChantier.SUSPENDU)
|
||||
.count();
|
||||
};
|
||||
public final LocalDateTime genereA = LocalDateTime.now();
|
||||
};
|
||||
|
||||
return handleFormatResponse(rapport, format, "rapport_financier_" + annee);
|
||||
}
|
||||
|
||||
// === EXPORTS SPÉCIALISÉS ===
|
||||
|
||||
@GET
|
||||
@Path("/export/csv/chantiers")
|
||||
@Produces("text/csv")
|
||||
@Operation(
|
||||
summary = "Export CSV des chantiers",
|
||||
description = "Exporte la liste des chantiers au format CSV")
|
||||
@APIResponse(responseCode = "200", description = "Export CSV généré")
|
||||
public Response exportCsvChantiers() {
|
||||
logger.info("Export CSV des chantiers");
|
||||
|
||||
List<Chantier> chantiers = chantierService.findAll();
|
||||
|
||||
StreamingOutput stream =
|
||||
output -> {
|
||||
try (PrintWriter writer = new PrintWriter(output)) {
|
||||
// En-têtes CSV
|
||||
writer.println(
|
||||
"ID,Nom,Description,Adresse,Statut,Date Début,Date Fin Prévue,Date Fin"
|
||||
+ " Réelle,Montant,Client");
|
||||
|
||||
// Données
|
||||
for (Chantier chantier : chantiers) {
|
||||
writer.printf(
|
||||
"%s,%s,%s,%s,%s,%s,%s,%s,%.2f,%s%n",
|
||||
csvEscape(chantier.getId().toString()),
|
||||
csvEscape(chantier.getNom()),
|
||||
csvEscape(chantier.getDescription()),
|
||||
csvEscape(chantier.getAdresse()),
|
||||
csvEscape(chantier.getStatut().toString()),
|
||||
chantier.getDateDebut(),
|
||||
chantier.getDateFinPrevue(),
|
||||
chantier.getDateFinReelle() != null ? chantier.getDateFinReelle() : "",
|
||||
chantier.getMontantPrevu() != null
|
||||
? chantier.getMontantPrevu().doubleValue()
|
||||
: 0.0,
|
||||
csvEscape(chantier.getClient() != null ? chantier.getClient().getNom() : ""));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Response.ok(stream)
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"chantiers_"
|
||||
+ LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
|
||||
+ ".csv\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export/csv/maintenance")
|
||||
@Produces("text/csv")
|
||||
@Operation(
|
||||
summary = "Export CSV des maintenances",
|
||||
description = "Exporte la liste des maintenances au format CSV")
|
||||
@APIResponse(responseCode = "200", description = "Export CSV généré")
|
||||
public Response exportCsvMaintenance() {
|
||||
logger.info("Export CSV des maintenances");
|
||||
|
||||
List<MaintenanceMateriel> maintenances = maintenanceService.findAll();
|
||||
|
||||
StreamingOutput stream =
|
||||
output -> {
|
||||
try (PrintWriter writer = new PrintWriter(output)) {
|
||||
// En-têtes CSV
|
||||
writer.println(
|
||||
"ID,Matériel,Type,Date Prévue,Date Réalisée,Technicien,Description,Statut");
|
||||
|
||||
// Données
|
||||
for (MaintenanceMateriel maintenance : maintenances) {
|
||||
writer.printf(
|
||||
"%s,%s,%s,%s,%s,%s,%s,%s%n",
|
||||
csvEscape(maintenance.getId().toString()),
|
||||
csvEscape(maintenance.getMateriel().getNom()),
|
||||
csvEscape(maintenance.getType().toString()),
|
||||
maintenance.getDatePrevue(),
|
||||
maintenance.getDateRealisee() != null ? maintenance.getDateRealisee() : "",
|
||||
csvEscape(maintenance.getTechnicien()),
|
||||
csvEscape(maintenance.getDescription()),
|
||||
csvEscape(maintenance.getStatut().toString()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Response.ok(stream)
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"maintenances_"
|
||||
+ LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
|
||||
+ ".csv\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES ===
|
||||
|
||||
private LocalDate parseDate(String dateStr, LocalDate defaultValue) {
|
||||
if (dateStr == null || dateStr.trim().isEmpty()) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(dateStr);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Date invalide: {}, utilisation de la valeur par défaut", dateStr);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private StatutChantier parseStatutChantier(String statutStr) {
|
||||
if (statutStr == null || statutStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return StatutChantier.valueOf(statutStr.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Statut de chantier invalide: {}", statutStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Response handleFormatResponse(Object data, String format, String filename) {
|
||||
switch (format.toLowerCase()) {
|
||||
case "csv":
|
||||
return convertToCSV(data, filename);
|
||||
case "json":
|
||||
default:
|
||||
return Response.ok(data).build();
|
||||
}
|
||||
}
|
||||
|
||||
private Response convertToCSV(Object data, String filename) {
|
||||
// Pour l'instant, retourne le JSON - l'implémentation CSV complète nécessiterait
|
||||
// une sérialisation plus complexe des objets
|
||||
logger.warn("Conversion CSV non implémentée, retour du JSON");
|
||||
return Response.ok(data)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Disposition", "attachment; filename=\"" + filename + ".json\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
private String csvEscape(String value) {
|
||||
if (value == null) return "";
|
||||
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
|
||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String formatMontant(double montant) {
|
||||
return String.format("%.2f €", montant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.TypeChantierService;
|
||||
import dev.lions.btpxpress.domain.core.entity.TypeChantier;
|
||||
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.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 types de chantier */
|
||||
@Path("/api/v1/types-chantier")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Types de Chantier", description = "Gestion des types de chantier BTP")
|
||||
public class TypeChantierResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TypeChantierResource.class);
|
||||
|
||||
@Inject TypeChantierService typeChantierService;
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les types de chantier")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des types de chantier récupérée avec succès")
|
||||
public Response getAllTypes(
|
||||
@Parameter(description = "Inclure les types inactifs")
|
||||
@QueryParam("includeInactive")
|
||||
@DefaultValue("false")
|
||||
boolean includeInactive) {
|
||||
try {
|
||||
List<TypeChantier> types =
|
||||
includeInactive
|
||||
? typeChantierService.findAllIncludingInactive()
|
||||
: typeChantierService.findAll();
|
||||
|
||||
logger.debug("Récupération de {} types de chantier", types.size());
|
||||
return Response.ok(types).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des types de chantier", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des types de chantier: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/par-categorie")
|
||||
@Operation(summary = "Récupérer les types de chantier groupés par catégorie")
|
||||
public Response getTypesByCategorie() {
|
||||
try {
|
||||
Map<String, List<TypeChantier>> types = typeChantierService.findByCategorie();
|
||||
return Response.ok(types).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération des types par catégorie", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un type de chantier par ID")
|
||||
@APIResponse(responseCode = "200", description = "Type de chantier récupéré avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Type de chantier non trouvé")
|
||||
public Response getTypeById(
|
||||
@Parameter(description = "ID du type de chantier") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID typeId = UUID.fromString(id);
|
||||
TypeChantier type = typeChantierService.findById(typeId);
|
||||
return Response.ok(type).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("ID invalide: " + id).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Type de chantier non trouvé avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du type de chantier {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/code/{code}")
|
||||
@Operation(summary = "Récupérer un type de chantier par code")
|
||||
public Response getTypeByCode(
|
||||
@Parameter(description = "Code du type de chantier") @PathParam("code") String code) {
|
||||
try {
|
||||
TypeChantier type = typeChantierService.findByCode(code);
|
||||
return Response.ok(type).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Type de chantier non trouvé avec le code: " + code)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la récupération du type de chantier par code {}", code, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer un nouveau type de chantier")
|
||||
@APIResponse(responseCode = "201", description = "Type de chantier créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response createType(@Valid TypeChantier typeChantier) {
|
||||
try {
|
||||
TypeChantier savedType = typeChantierService.create(typeChantier);
|
||||
logger.info("Type de chantier créé avec succès: {}", savedType.getCode());
|
||||
return Response.status(Response.Status.CREATED).entity(savedType).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.CONFLICT).entity("Conflit: " + e.getMessage()).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la création du type de chantier", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un type de chantier")
|
||||
public Response updateType(
|
||||
@Parameter(description = "ID du type de chantier") @PathParam("id") String id,
|
||||
@Valid TypeChantier typeChantier) {
|
||||
try {
|
||||
UUID typeId = UUID.fromString(id);
|
||||
TypeChantier updatedType = typeChantierService.update(typeId, typeChantier);
|
||||
logger.info("Type de chantier mis à jour avec succès: {}", updatedType.getCode());
|
||||
return Response.ok(updatedType).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Données invalides: " + e.getMessage())
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Type de chantier non trouvé avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la mise à jour du type de chantier {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la mise à jour: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer un type de chantier (soft delete)")
|
||||
public Response deleteType(
|
||||
@Parameter(description = "ID du type de chantier") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID typeId = UUID.fromString(id);
|
||||
typeChantierService.delete(typeId);
|
||||
logger.info("Type de chantier supprimé (soft delete): {}", id);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("ID invalide: " + id).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Type de chantier non trouvé avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la suppression du type de chantier {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reactivate")
|
||||
@Operation(summary = "Réactiver un type de chantier")
|
||||
public Response reactivateType(
|
||||
@Parameter(description = "ID du type de chantier") @PathParam("id") String id) {
|
||||
try {
|
||||
UUID typeId = UUID.fromString(id);
|
||||
TypeChantier reactivatedType = typeChantierService.reactivate(typeId);
|
||||
return Response.ok(reactivatedType).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("ID invalide: " + id).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity("Type de chantier non trouvé avec l'ID: " + id)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Erreur lors de la réactivation du type de chantier {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la réactivation: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupérer les statistiques des types de chantier")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
Map<String, Object> stats = typeChantierService.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("Erreur lors de la récupération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
495
src/main/java/dev/lions/btpxpress/adapter/http/UserResource.java
Normal file
495
src/main/java/dev/lions/btpxpress/adapter/http/UserResource.java
Normal file
@@ -0,0 +1,495 @@
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des utilisateurs - Architecture 2025 SÉCURITÉ: Accès restreint aux
|
||||
* administrateurs
|
||||
*/
|
||||
@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;
|
||||
|
||||
// === ENDPOINTS DE CONSULTATION ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupérer tous les utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des utilisateurs récupérée avec succès")
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé - droits administrateur requis")
|
||||
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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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 (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", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération des utilisateurs: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un utilisateur par ID")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur récupéré avec succès")
|
||||
@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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Utilisateur non trouvé avec l'ID: " + id)
|
||||
.build());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID d'utilisateur invalide: " + id)
|
||||
.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 de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la récupération de l'utilisateur: " + e.getMessage())
|
||||
.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 = "Obtenir les statistiques des utilisateurs")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès")
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé")
|
||||
public Response getUserStats(
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
try {
|
||||
Object stats = userService.getStatistics();
|
||||
return Response.ok(stats).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 génération des statistiques utilisateurs", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE GESTION ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Créer 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Données invalides: " + e.getMessage())
|
||||
.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 création de l'utilisateur", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la création de l'utilisateur: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Modifier un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Utilisateur modifié 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Données invalides: " + e.getMessage())
|
||||
.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 modification de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification de l'utilisateur: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/status")
|
||||
@Operation(summary = "Modifier le statut d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Statut modifié 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Statut invalide: " + e.getMessage())
|
||||
.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 modification du statut utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification du statut: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/role")
|
||||
@Operation(summary = "Modifier le rôle d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Rôle modifié 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Rôle invalide: " + e.getMessage())
|
||||
.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 modification du rôle utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la modification du rôle: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/approve")
|
||||
@Operation(summary = "Approuver 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
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("Données invalides: " + e.getMessage())
|
||||
.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 l'approbation de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de l'approbation de l'utilisateur: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reject")
|
||||
@Operation(summary = "Rejeter 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
userService.rejectUser(userId, request.reason);
|
||||
|
||||
return Response.ok().entity("Utilisateur rejeté avec succès").build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("Données invalides: " + e.getMessage())
|
||||
.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 rejet de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors du rejet de l'utilisateur: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer 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,
|
||||
@Parameter(description = "Token d'authentification") @HeaderParam("Authorization")
|
||||
String authorizationHeader) {
|
||||
try {
|
||||
UUID userId = UUID.fromString(id);
|
||||
userService.deleteUser(userId);
|
||||
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("ID invalide: " + e.getMessage())
|
||||
.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 suppression de l'utilisateur {}", id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de l'utilisateur: " + e.getMessage())
|
||||
.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) {}
|
||||
}
|
||||
Reference in New Issue
Block a user