diff --git a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
index 08e310e..f21a671 100644
--- a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
+++ b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
@@ -1,5 +1,6 @@
package de.lions.unionflow.server.auth;
+import io.quarkus.security.PermitAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@@ -11,6 +12,7 @@ import org.jboss.logging.Logger;
* mobile.
*/
@Path("/auth")
+@PermitAll
public class AuthCallbackResource {
private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
diff --git a/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java b/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java
new file mode 100644
index 0000000..0af03c1
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java
@@ -0,0 +1,62 @@
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.*;
+
+/**
+ * Barème de cotisation par rôle fonctionnel au sein d'une organisation.
+ *
+ *
Permet de définir des montants différenciés selon le rôle du membre
+ * (PRESIDENT, TRESORIER, MEMBRE_ORDINAIRE, etc.).
+ *
+ *
Si aucun barème n'est défini pour un rôle donné, le système utilise
+ * le montant par défaut de {@link ParametresCotisationOrganisation}.
+ *
+ *
Table : {@code bareme_cotisation_role}
+ */
+@Entity
+@Table(
+ name = "bareme_cotisation_role",
+ uniqueConstraints = @UniqueConstraint(
+ name = "uq_bareme_cot_org_role",
+ columnNames = {"organisation_id", "role_org"}
+ )
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class BaremeCotisationRole extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /**
+ * Rôle fonctionnel dans l'organisation (ex: PRESIDENT, TRESORIER, SECRETAIRE, MEMBRE_ORDINAIRE).
+ * Correspond à {@link dev.lions.unionflow.server.entity.MembreOrganisation#getRoleOrg()}.
+ */
+ @NotBlank
+ @Column(name = "role_org", nullable = false, length = 50)
+ private String roleOrg;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_mensuel", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantMensuel = BigDecimal.ZERO;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_annuel", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantAnnuel = BigDecimal.ZERO;
+
+ /** Description optionnelle du barème (ex: "Taux réduit bureau exécutif"). */
+ @Column(name = "description", length = 255)
+ private String description;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
index d58f375..bd314eb 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
@@ -73,6 +73,15 @@ public class ParametresCotisationOrganisation extends BaseEntity {
@Column(name = "cotisation_obligatoire", nullable = false)
private Boolean cotisationObligatoire = true;
+ /**
+ * Active la génération automatique mensuelle des cotisations pour cette organisation.
+ * Quand {@code true}, un job planifié crée automatiquement une cotisation par membre actif
+ * le 1er de chaque mois, en utilisant les barèmes par rôle ou le montant par défaut.
+ */
+ @Builder.Default
+ @Column(name = "generation_automatique_activee", nullable = false)
+ private Boolean generationAutomatiqueActivee = false;
+
// ── Méthodes métier ────────────────────────────────────────────────────────
/**
diff --git a/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java b/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java
new file mode 100644
index 0000000..03e6111
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java
@@ -0,0 +1,20 @@
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.BaremeCotisationRole;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+@ApplicationScoped
+public class BaremeCotisationRoleRepository implements PanacheRepositoryBase {
+
+ public Optional findByOrganisationIdAndRoleOrg(UUID organisationId, String roleOrg) {
+ return find("organisation.id = ?1 AND roleOrg = ?2", organisationId, roleOrg).firstResultOptional();
+ }
+
+ public List findByOrganisationId(UUID organisationId) {
+ return find("organisation.id = ?1", organisationId).list();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
index 197f34c..ad37ea1 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
@@ -601,4 +601,22 @@ public class CotisationRepository extends BaseRepository {
}
return orderBy.toString();
}
+
+ /**
+ * Vérifie si une cotisation mensuelle existe déjà pour ce membre, cette organisation,
+ * cette année et ce mois. Utilisé pour éviter les doublons lors de la génération automatique.
+ */
+ public boolean existsByMembreOrganisationAnneeAndMois(UUID membreId, UUID organisationId, int annee, int mois) {
+ Long count = entityManager.createQuery(
+ "SELECT COUNT(c) FROM Cotisation c "
+ + "WHERE c.membre.id = :membreId AND c.organisation.id = :orgId "
+ + "AND c.annee = :annee AND c.mois = :mois",
+ Long.class)
+ .setParameter("membreId", membreId)
+ .setParameter("orgId", organisationId)
+ .setParameter("annee", annee)
+ .setParameter("mois", mois)
+ .getSingleResult();
+ return count != null && count > 0;
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
index ba830a9..1b16a4f 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
@@ -66,6 +66,16 @@ public class DocumentRepository implements PanacheRepositoryBase
public List findAllActifs() {
return find("actif = true ORDER BY dateCreation DESC").list();
}
+
+ /**
+ * Trouve les documents créés par un utilisateur (par email)
+ *
+ * @param email Email de l'utilisateur (creePar)
+ * @return Liste des documents
+ */
+ public List findByCreePar(String email) {
+ return find("creePar = ?1 AND actif = true ORDER BY dateCreation DESC", email).list();
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java
new file mode 100644
index 0000000..5c60dd1
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java
@@ -0,0 +1,22 @@
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.ParametresCotisationOrganisation;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+@ApplicationScoped
+public class ParametresCotisationOrganisationRepository
+ implements PanacheRepositoryBase {
+
+ public Optional findByOrganisationId(UUID organisationId) {
+ return find("organisation.id = ?1", organisationId).firstResultOptional();
+ }
+
+ /** Retourne toutes les organisations ayant activé la génération automatique. */
+ public List findAvecGenerationAutomatiqueActivee() {
+ return find("generationAutomatiqueActivee = true AND actif = true").list();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
index dda8cc4..f8f7d04 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
@@ -50,7 +50,7 @@ public class AnalyticsResource {
/** Calcule une métrique analytics pour une période donnée */
@GET
@Path("/metriques/{typeMetrique}")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données")
@@ -88,7 +88,7 @@ public class AnalyticsResource {
/** Calcule les tendances d'un KPI sur une période */
@GET
@Path("/tendances/{typeMetrique}")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
@@ -127,7 +127,7 @@ public class AnalyticsResource {
/** Obtient tous les KPI pour une organisation */
@GET
@Path("/kpis")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données")
@@ -163,7 +163,7 @@ public class AnalyticsResource {
/** Calcule le KPI de performance globale */
@GET
@Path("/performance-globale")
- @RolesAllowed({"ADMIN", "MANAGER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation")
@@ -208,7 +208,7 @@ public class AnalyticsResource {
/** Obtient les évolutions des KPI par rapport à la période précédente */
@GET
@Path("/evolutions")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente")
@@ -248,7 +248,7 @@ public class AnalyticsResource {
/** Obtient les widgets du tableau de bord pour un utilisateur */
@GET
@Path("/dashboard/widgets")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
@@ -285,7 +285,7 @@ public class AnalyticsResource {
/** Obtient les types de métriques disponibles */
@GET
@Path("/types-metriques")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles")
@@ -300,7 +300,7 @@ public class AnalyticsResource {
/** Obtient les périodes d'analyse disponibles */
@GET
@Path("/periodes-analyse")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
diff --git a/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java b/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
index df2bb76..1b21d57 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
@@ -1,6 +1,7 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.ApprovalService;
+import dev.lions.unionflow.server.api.dto.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;
@@ -53,7 +54,7 @@ public class ApprovalResource {
if (transactionId == null || transactionType == null || amount == null) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("transactionId, transactionType et amount sont requis"))
+ .entity(ErrorResponse.of("transactionId, transactionType et amount sont requis"))
.build();
}
@@ -64,12 +65,12 @@ public class ApprovalResource {
return Response.status(Response.Status.CREATED).entity(approval).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -88,7 +89,7 @@ public class ApprovalResource {
} 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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -106,12 +107,12 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -131,16 +132,16 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (ForbiddenException e) {
return Response.status(Response.Status.FORBIDDEN)
- .entity(new ErrorResponse(e.getMessage()))
+ .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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -160,16 +161,16 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (ForbiddenException e) {
return Response.status(Response.Status.FORBIDDEN)
- .entity(new ErrorResponse(e.getMessage()))
+ .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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -197,12 +198,12 @@ public class ApprovalResource {
return Response.ok(approvals).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .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(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -221,12 +222,11 @@ public class ApprovalResource {
} catch (Exception e) {
LOG.error("Erreur lors du comptage des approbations", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
- // Classes internes pour les réponses
- record ErrorResponse(String message) {}
+ // Classe interne pour le comptage
record CountResponse(long count) {}
}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java b/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
index faa65e8..5b7adac 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
@@ -36,7 +36,7 @@ public class BackupResource {
* Lister toutes les sauvegardes
*/
@GET
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Lister toutes les sauvegardes disponibles")
public List getAllBackups() {
log.info("GET /api/backups");
@@ -48,7 +48,7 @@ public class BackupResource {
*/
@GET
@Path("/{id}")
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer une sauvegarde par ID")
public BackupResponse getBackupById(@PathParam("id") UUID id) {
log.info("GET /api/backups/{}", id);
@@ -98,7 +98,7 @@ public class BackupResource {
*/
@GET
@Path("/config")
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des sauvegardes automatiques")
public BackupConfigResponse getBackupConfig() {
log.info("GET /api/backups/config");
diff --git a/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java b/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
index da8f7f3..a109c14 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
@@ -1,6 +1,7 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.BudgetService;
+import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.CreateBudgetRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.BudgetResponse;
import jakarta.annotation.security.RolesAllowed;
@@ -51,12 +52,12 @@ public class BudgetResource {
return Response.ok(budgets).build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération des budgets", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -74,12 +75,12 @@ public class BudgetResource {
return Response.ok(budget).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -98,16 +99,16 @@ public class BudgetResource {
.build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la création du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -125,12 +126,12 @@ public class BudgetResource {
return Response.ok(tracking).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération du suivi budgétaire", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -150,16 +151,16 @@ public class BudgetResource {
return Response.ok(budget).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la mise à jour du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -177,16 +178,15 @@ public class BudgetResource {
return Response.noContent().build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la suppression du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
- // Classe interne pour les réponses d'erreur
- record ErrorResponse(String message) {}
}
+
diff --git a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
index 57782a0..d2c08de 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
@@ -2,6 +2,7 @@ package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.comptabilite.request.*;
import dev.lions.unionflow.server.api.dto.comptabilite.response.*;
+import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
import dev.lions.unionflow.server.service.ComptabiliteService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
@@ -52,12 +53,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création du compte comptable: " + e.getMessage()))
.build();
}
}
@@ -76,12 +77,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Compte comptable non trouvé"))
+ .entity(ErrorResponse.ofError("Compte comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
.build();
}
}
@@ -100,7 +101,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des comptes comptables");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
.build();
}
}
@@ -124,12 +125,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création du journal comptable: " + e.getMessage()))
.build();
}
}
@@ -148,12 +149,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Journal comptable non trouvé"))
+ .entity(ErrorResponse.ofError("Journal comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
.build();
}
}
@@ -172,7 +173,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des journaux comptables");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
.build();
}
}
@@ -196,12 +197,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
.build();
}
}
@@ -220,12 +221,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Écriture comptable non trouvée"))
+ .entity(ErrorResponse.ofError("Écriture comptable non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
.build();
}
}
@@ -245,7 +246,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
@@ -265,17 +266,10 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
- /** Classe interne pour les réponses d'erreur */
- public static class ErrorResponse {
- public String error;
-
- public ErrorResponse(String error) {
- this.error = error;
- }
- }
}
+
diff --git a/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java b/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
index ce7638a..42a5dc4 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
@@ -247,4 +247,55 @@ public class CompteAdherentResource {
membreKeycloakSyncService.changerMotDePassePremierLogin(membreOpt.get().getId(), nouveauMotDePasse);
return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
}
+
+ /**
+ * Endpoint mobile : changement de mot de passe depuis l'app Flutter.
+ * Bypass lions-user-manager — appel direct à l'API Admin Keycloak.
+ *
+ * Body attendu : {@code { "userId": "...", "oldPassword": "...", "newPassword": "..." }}
+ */
+ @POST
+ @Path("/auth/change-password")
+ @Authenticated
+ @Operation(
+ summary = "Changer le mot de passe (mobile)",
+ description = "Endpoint dédié à l'application mobile. Bypass lions-user-manager via API Admin Keycloak directe."
+ )
+ public Response changerMotDePasseMobile(Map body) {
+ String email = securiteHelper.resolveEmail();
+ if (email == null || email.isBlank()) {
+ return Response.status(Response.Status.UNAUTHORIZED).build();
+ }
+
+ String newPassword = body == null ? null : body.get("newPassword");
+ if (newPassword == null || newPassword.isBlank()) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("message", "Le champ 'newPassword' est requis."))
+ .build();
+ }
+ if (newPassword.length() < 8) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("message", "Le mot de passe doit contenir au moins 8 caractères."))
+ .build();
+ }
+
+ Optional membreOpt = membreRepository.findByEmail(email.trim())
+ .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));
+
+ if (membreOpt.isEmpty()) {
+ return Response.status(Response.Status.NOT_FOUND)
+ .entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
+ .build();
+ }
+
+ try {
+ membreKeycloakSyncService.changerMotDePasseDirectKeycloak(membreOpt.get().getId(), newPassword);
+ return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
+ } catch (Exception e) {
+ LOG.errorf(e, "Erreur changement mot de passe pour %s", email);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("message", "Erreur lors du changement de mot de passe: " + e.getMessage()))
+ .build();
+ }
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
index e7bcb8f..22ee1f8 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
@@ -60,19 +60,19 @@ public class CotisationResource {
try {
log.info("GET /api/cotisations/public - page: {}, size: {}", page, size);
- List cotisations = cotisationService.getAllCotisations(page, size);
+ List cotisations = cotisationService.getAllCotisations(page, size);
List