Initial commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user