package dev.lions.unionflow.server.resource; import dev.lions.unionflow.server.service.ApprovalService; import dev.lions.unionflow.server.common.ErrorResponse; import dev.lions.unionflow.server.api.dto.finance_workflow.request.ApproveTransactionRequest; import dev.lions.unionflow.server.api.dto.finance_workflow.request.RejectTransactionRequest; import dev.lions.unionflow.server.api.dto.finance_workflow.response.TransactionApprovalResponse; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.jboss.logging.Logger; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; /** * Resource REST pour la gestion des approbations de transactions * * @author UnionFlow Team * @version 1.0 * @since 2026-03-13 */ @Path("/api/finance/approvals") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Finance - Approvals", description = "Gestion des approbations de transactions financières") public class ApprovalResource { private static final Logger LOG = Logger.getLogger(ApprovalResource.class); @Inject ApprovalService approvalService; @POST @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN", "MEMBRE"}) @Operation(summary = "Demande une approbation de transaction", description = "Crée une demande d'approbation pour une transaction financière") public Response requestApproval(Map request) { LOG.infof("POST /api/finance/approvals - Request approval"); try { String transactionId = (String) request.get("transactionId"); String transactionType = (String) request.get("transactionType"); Number rawAmount = (Number) request.get("amount"); Double amount = rawAmount != null ? rawAmount.doubleValue() : null; String organizationIdStr = (String) request.get("organizationId"); if (transactionId == null || transactionType == null || amount == null) { return Response.status(Response.Status.BAD_REQUEST) .entity(ErrorResponse.of("transactionId, transactionType et amount sont requis")) .build(); } UUID organizationId = organizationIdStr != null ? UUID.fromString(organizationIdStr) : null; TransactionApprovalResponse approval = approvalService.requestApproval( UUID.fromString(transactionId), transactionType, amount, organizationId); return Response.status(Response.Status.CREATED).entity(approval).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (Exception e) { LOG.error("Erreur lors de la création de la demande d'approbation", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @GET @Path("/pending") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Récupère les approbations en attente", description = "Liste toutes les approbations de transactions en attente pour une organisation") public Response getPendingApprovals(@QueryParam("organizationId") UUID organizationId) { LOG.infof("GET /api/finance/approvals/pending?organizationId=%s", organizationId); try { List approvals = approvalService.getPendingApprovals(organizationId); return Response.ok(approvals).build(); } catch (Exception e) { LOG.error("Erreur lors de la récupération des approbations en attente", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @GET @Path("/{approvalId}") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Récupère une approbation par ID", description = "Retourne les détails d'une approbation spécifique") public Response getApprovalById(@PathParam("approvalId") UUID approvalId) { LOG.infof("GET /api/finance/approvals/%s", approvalId); try { TransactionApprovalResponse approval = approvalService.getApprovalById(approvalId); return Response.ok(approval).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (Exception e) { LOG.error("Erreur lors de la récupération de l'approbation", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @POST @Path("/{approvalId}/approve") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Approuve une transaction", description = "Approuve une demande de transaction avec un commentaire optionnel") public Response approveTransaction( @PathParam("approvalId") UUID approvalId, @Valid ApproveTransactionRequest request) { LOG.infof("POST /api/finance/approvals/%s/approve", approvalId); try { TransactionApprovalResponse approval = approvalService.approveTransaction(approvalId, request); return Response.ok(approval).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (ForbiddenException e) { return Response.status(Response.Status.FORBIDDEN) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (Exception e) { LOG.error("Erreur lors de l'approbation de la transaction", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @POST @Path("/{approvalId}/reject") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Rejette une transaction", description = "Rejette une demande de transaction avec une raison obligatoire") public Response rejectTransaction( @PathParam("approvalId") UUID approvalId, @Valid RejectTransactionRequest request) { LOG.infof("POST /api/finance/approvals/%s/reject", approvalId); try { TransactionApprovalResponse approval = approvalService.rejectTransaction(approvalId, request); return Response.ok(approval).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (ForbiddenException e) { return Response.status(Response.Status.FORBIDDEN) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (Exception e) { LOG.error("Erreur lors du rejet de la transaction", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @GET @Path("/history") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Récupère l'historique des approbations", description = "Liste l'historique des approbations avec filtres optionnels") public Response getApprovalsHistory( @QueryParam("organizationId") UUID organizationId, @QueryParam("startDate") String startDateStr, @QueryParam("endDate") String endDateStr, @QueryParam("status") String status) { LOG.infof("GET /api/finance/approvals/history?organizationId=%s&status=%s", organizationId, status); try { LocalDateTime startDate = startDateStr != null ? LocalDateTime.parse(startDateStr) : null; LocalDateTime endDate = endDateStr != null ? LocalDateTime.parse(endDateStr) : null; List approvals = approvalService.getApprovalsHistory( organizationId, startDate, endDate, status); return Response.ok(approvals).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(ErrorResponse.of(e.getMessage())) .build(); } catch (Exception e) { LOG.error("Erreur lors de la récupération de l'historique", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } @GET @Path("/count/pending") @RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"}) @Operation(summary = "Compte les approbations en attente", description = "Retourne le nombre d'approbations en attente pour une organisation") public Response countPendingApprovals(@QueryParam("organizationId") UUID organizationId) { LOG.infof("GET /api/finance/approvals/count/pending?organizationId=%s", organizationId); try { long count = approvalService.countPendingApprovals(organizationId); return Response.ok(new CountResponse(count)).build(); } catch (Exception e) { LOG.error("Erreur lors du comptage des approbations", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(ErrorResponse.of(e.getMessage())) .build(); } } // Classe interne pour le comptage record CountResponse(long count) {} }