37 Commits

Author SHA1 Message Date
e5e17d3862 Merge pull request 'fix: null licence on import' (#63) from dev into master
Reviewed-on: #63
2025-11-14 13:32:09 +00:00
7410569ced fix: null licence on import
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m3s
2025-11-14 14:31:37 +01:00
b107f443aa Merge pull request 'fix: add more log to import' (#62) from dev into master
Reviewed-on: #62
2025-11-14 13:15:27 +00:00
8e2d68ebd5 fix: add more log to import
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m10s
2025-11-14 14:14:22 +01:00
f050127fd7 Merge pull request 'dev' (#61) from dev into master
Reviewed-on: #61
2025-11-14 12:45:53 +00:00
d02fd63834 fix: empty mail on import
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m30s
2025-11-14 13:43:51 +01:00
b143cc759f feat: remove kc new account mail 2025-11-12 16:38:35 +01:00
cc5534ef00 feat: add cache to getAssoInfo 2025-11-12 16:15:40 +01:00
26f56006f6 feat: merge rna and siret fields 2025-11-12 14:56:28 +01:00
f8dacee3e7 Merge pull request 'fix: club delete on RegisterModel' (#60) from dev into master
Reviewed-on: #60
2025-11-08 18:09:04 +00:00
ed1f30f2b6 fix: club delete on RegisterModel
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m17s
2025-11-08 19:08:41 +01:00
8517e9824c Merge pull request 'feat: allow empty mail' (#59) from dev into master
Reviewed-on: #59
2025-11-08 17:45:02 +00:00
6cec8ff31d feat: allow empty mail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m16s
2025-11-08 18:44:35 +01:00
e7deba52e9 Merge pull request 'dev' (#58) from dev into master
Reviewed-on: #58
2025-11-07 15:31:18 +00:00
d95c173fa8 fix: aff renew select length
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m51s
2025-11-07 16:19:49 +01:00
8a0e4423f2 feat: remove saison selection on aff req 2025-11-07 16:13:37 +01:00
b956236934 fix: affiliation ok login msg 2025-11-07 15:57:36 +01:00
7767c98304 feat: add email tooltip 2025-11-07 15:55:10 +01:00
7e380ccb69 feat: add re-login message 2025-11-07 15:31:25 +01:00
94d1148eb1 fix: null email on import 2025-11-07 15:30:56 +01:00
8a14f58ce5 Merge pull request 'fix: log detail' (#57) from dev into master
Reviewed-on: #57
2025-11-05 21:06:54 +00:00
79dbbdaaec fix: log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m27s
2025-11-05 22:06:36 +01:00
bf5704db54 Merge pull request 'fix: log detail' (#56) from dev into master
Reviewed-on: #56
2025-11-05 20:43:26 +00:00
dec98f9508 fix: log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m22s
2025-11-05 21:43:05 +01:00
d3a62e980d Merge pull request 'feat: add aff req log detail' (#55) from dev into master
Reviewed-on: #55
2025-11-05 20:19:10 +00:00
b89ed62795 feat: add aff req log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m33s
fix: membre import null email filter
2025-11-05 21:18:29 +01:00
5ffc9fb495 Merge pull request 'fix: typo' (#54) from dev into master
Reviewed-on: #54
2025-09-05 18:31:45 +00:00
5e48bc4623 fix: typo
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 9m51s
2025-09-05 20:31:10 +02:00
81b953fb05 Merge pull request 'dev' (#53) from dev into master
Reviewed-on: #53
2025-09-03 19:47:13 +00:00
c6659f8d85 feat: keep log
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m19s
2025-09-03 21:46:26 +02:00
e86fe42b3d fix: club order 2025-09-03 21:35:36 +02:00
ef528aa524 fix: log message length 2025-09-03 19:53:19 +02:00
e6cc4cbc96 Merge pull request 'dev' (#52) from dev into master
Reviewed-on: #52
2025-08-21 09:43:54 +00:00
c58dedf80a Merge pull request 'competition rework' (#51) from dev into master
Reviewed-on: #51
2025-08-18 09:33:51 +00:00
1908de681e Merge pull request 'dev' (#50) from dev into master
Reviewed-on: #50
2025-08-15 19:49:13 +00:00
76381c75bd Merge pull request 'fix: Membre edition' (#49) from dev into master
Reviewed-on: #49
2025-08-15 18:34:43 +00:00
7d281196c1 Merge pull request 'dev' (#48) from dev into master
Reviewed-on: #48
2025-08-15 16:26:23 +00:00
47 changed files with 375 additions and 377 deletions

View File

@@ -76,6 +76,7 @@ jobs:
key: ${{ secrets.SSH_KEY }} key: ${{ secrets.SSH_KEY }}
script: | script: |
cd ${{ secrets.TARGET_DIR }} cd ${{ secrets.TARGET_DIR }}
docker logs ffsaf > "log/ffsaf_logs_$(date +"%Y-%m-%d_%H-%M-%S").log" 2>&1
docker stop ffsaf docker stop ffsaf
docker rm ffsaf docker rm ffsaf
docker compose up --build -d ffsaf docker compose up --build -d ffsaf

View File

@@ -21,8 +21,7 @@ public class AffiliationRequestModel {
Long id; Long id;
String name; String name;
long siret; String state_id;
String RNA;
String address; String address;
String contact; String contact;

View File

@@ -55,11 +55,8 @@ public class ClubModel implements LoggableModel {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris") @Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
String address; String address;
@Schema(description = "RNA du club", example = "W123456789") @Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
String RNA; String StateId;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345") @Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation; Long no_affiliation;

View File

@@ -30,8 +30,10 @@ public class LogModel {
Long target_id; Long target_id;
@Column(columnDefinition = "TEXT")
String target_name; String target_name;
@Column(columnDefinition = "TEXT")
String message; String message;
public enum ActionType { public enum ActionType {

View File

@@ -82,4 +82,22 @@ public class MembreModel implements LoggableModel {
public LogModel.ObjectType getObjectType() { public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Membre; return LogModel.ObjectType.Membre;
} }
@Override
public String toString() {
return "MembreModel{" +
"id=" + id +
", userId='" + userId + '\'' +
", lname='" + lname + '\'' +
", fname='" + fname + '\'' +
", categorie=" + categorie +
", genre=" + genre +
", licence=" + licence +
", country='" + country + '\'' +
", birth_date=" + birth_date +
", email='" + email + '\'' +
", role=" + role +
", grade_arbitrage=" + grade_arbitrage +
'}';
}
} }

View File

@@ -8,6 +8,8 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Getter @Getter
@Setter @Setter
@@ -38,6 +40,7 @@ public class RegisterModel {
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club") @JoinColumn(name = "club")
@OnDelete(action = OnDeleteAction.SET_NULL)
ClubModel club = null; ClubModel club = null;
@Column(nullable = false, columnDefinition = "boolean default false") @Column(nullable = false, columnDefinition = "boolean default false")

View File

@@ -22,8 +22,7 @@ public class ClubEntity {
private String training_location; private String training_location;
private String training_day_time; private String training_day_time;
private String contact_intern; private String contact_intern;
private String RNA; private String StateId;
private Long SIRET;
private Long no_affiliation; private Long no_affiliation;
private boolean international; private boolean international;
@@ -41,8 +40,7 @@ public class ClubEntity {
.training_location(model.getTraining_location()) .training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time()) .training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern()) .contact_intern(model.getContact_intern())
.RNA(model.getRNA()) .StateId(model.getStateId())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation()) .no_affiliation(model.getNo_affiliation())
.international(model.isInternational()) .international(model.isInternational())
.build(); .build();

View File

@@ -22,6 +22,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -30,6 +31,7 @@ import java.util.stream.Stream;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class AffiliationService { public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject @Inject
CombRepository combRepository; CombRepository combRepository;
@@ -78,14 +80,14 @@ public class AffiliationService {
throw new DBadRequestException("Saison non valid"); throw new DBadRequestException("Saison non valid");
} }
})) }))
.chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(), .chain(() -> repositoryRequest.count("state_id = ?1 and saison = ?2", affModel.getState_id(),
affModel.getSaison())) affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> { .onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) { if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante"); throw new DBadRequestException("Demande d'affiliation déjà existante");
} }
})) }))
.chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club -> .chain(() -> clubRepository.find("StateId = ?1", affModel.getState_id()).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison()))) repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> { .onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) { if (count != 0) {
@@ -122,7 +124,6 @@ public class AffiliationService {
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(origine -> { .chain(origine -> {
origine.setName(model.getName()); origine.setName(model.getName());
origine.setRNA(model.getRNA());
origine.setAddress(model.getAddress()); origine.setAddress(model.getAddress());
origine.setContact(model.getContact()); origine.setContact(model.getContact());
origine.setM1_lname(model.getM1_lname()); origine.setM1_lname(model.getM1_lname());
@@ -146,6 +147,9 @@ public class AffiliationService {
} }
public Uni<String> save(AffiliationRequestForm form) { public Uni<String> save(AffiliationRequestForm form) {
LOGGER.debug("Affiliation Request Created");
LOGGER.debug(form.toString());
// noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher // noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher
return pre_save(form, true) return pre_save(form, true)
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model))) .chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
@@ -169,12 +173,14 @@ public class AffiliationService {
} }
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) { public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Saved");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId()) return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> { .map(model -> {
model.setName(form.getName()); model.setName(form.getName());
model.setSiret(form.getSiret()); model.setState_id(form.getState_id());
model.setRNA(form.getRna());
model.setAddress(form.getAddress()); model.setAddress(form.getAddress());
model.setContact(form.getContact()); model.setContact(form.getContact());
@@ -259,7 +265,9 @@ public class AffiliationService {
}).call(m -> Panache.withTransaction(() -> combRepository.persist(m))); }).call(m -> Panache.withTransaction(() -> combRepository.persist(m)));
} }
}) })
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId()) : .call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId())
.onFailure().invoke(t -> LOGGER.warnf("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull() :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId())) keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage())) .call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage()))
.call(userId -> keycloakService.setEmail(userId, m.getEmail()))) .call(userId -> keycloakService.setEmail(userId, m.getEmail())))
@@ -267,19 +275,24 @@ public class AffiliationService {
.call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ? .call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ?
Uni.createFrom().nullItem() : Uni.createFrom().nullItem() :
Panache.withTransaction(() -> licenceRepository.persist( Panache.withTransaction(() -> licenceRepository.persist(
new LicenceModel(null, m, club.getId(), saison, null, true, false))) new LicenceModel(null, m, club.getId(), saison, null, true, false)))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(), .call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(),
licenceModel)))); licenceModel))));
} }
public Uni<?> accept(AffiliationRequestSaveForm form) { public Uni<?> accept(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Accepted");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId()) return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req -> .chain(req ->
clubRepository.find("SIRET = ?1", form.getSiret()).firstResult() clubRepository.find("StateId = ?1", form.getState_id()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model)) .chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
.call(club -> setMembre(form.new Member(1), club, req.getSaison()) .call(club -> setMembre(form.new Member(1), club, req.getSaison()).onFailure()
.call(__ -> setMembre(form.new Member(2), club, req.getSaison()) .recoverWithNull()
.call(__ -> setMembre(form.new Member(2), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(___ -> setMembre(form.new Member(3), club, req.getSaison())))) .call(___ -> setMembre(form.new Member(3), club, req.getSaison()))))
.onItem() .onItem()
.invoke(model -> Uni.createFrom() .invoke(model -> Uni.createFrom()
@@ -298,13 +311,13 @@ public class AffiliationService {
} }
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) { private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem() return Uni.createFrom().nullItem()
.chain(() -> { .chain(() -> {
ClubModel club = new ClubModel(); ClubModel club = new ClubModel();
club.setName(form.getName()); club.setName(form.getName());
club.setCountry("FR"); club.setCountry("FR");
club.setSIRET(form.getSiret()); club.setStateId(form.getState_id());
club.setRNA(form.getRna());
club.setAddress(form.getAddress()); club.setAddress(form.getAddress());
club.setContact_intern(form.getContact()); club.setContact_intern(form.getContact());
club.setAffiliations(new ArrayList<>()); club.setAffiliations(new ArrayList<>());
@@ -336,12 +349,12 @@ public class AffiliationService {
} }
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) { private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem() return Uni.createFrom().nullItem()
.chain(() -> { .chain(() -> {
club.setName(form.getName()); club.setName(form.getName());
club.setCountry("FR"); club.setCountry("FR");
club.setSIRET(form.getSiret()); club.setStateId(form.getState_id());
club.setRNA(form.getRna());
club.setAddress(form.getAddress()); club.setAddress(form.getAddress());
club.setContact_intern(form.getContact()); club.setContact_intern(form.getContact());
return Panache.withTransaction(() -> clubRepository.persist(club) return Panache.withTransaction(() -> clubRepository.persist(club)
@@ -354,7 +367,7 @@ public class AffiliationService {
public Uni<SimpleReqAffiliation> getRequest(long id) { public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel) return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.call(out -> clubRepository.find("SIRET = ?1", out.getSiret()).firstResult().invoke(c -> { .call(out -> clubRepository.find("StateId = ?1", out.getStateId()).firstResult().invoke(c -> {
if (c != null) { if (c != null) {
out.setClub(c.getId()); out.setClub(c.getId());
out.setClub_name(c.getName()); out.setClub_name(c.getName());
@@ -367,7 +380,7 @@ public class AffiliationService {
public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliation() { public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliation() {
return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison()) return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison())
.map(models -> models.stream() .map(models -> models.stream()
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getSiret(), model.getSaison(), .map(model -> new SimpleAffiliation(model.getId() * -1, model.getState_id(), model.getSaison(),
false)).toList()) false)).toList())
.chain(aff -> repository.list("saison = ?1", Utils.getSaison()) .chain(aff -> repository.list("saison = ?1", Utils.getSaison())
.map(models -> models.stream().map(SimpleAffiliation::fromModel).toList()) .map(models -> models.stream().map(SimpleAffiliation::fromModel).toList())
@@ -379,9 +392,9 @@ public class AffiliationService {
return clubRepository.findById(id) return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations())) .call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("siret = ?1", model.getSIRET()) .chain(model -> repositoryRequest.list("state_id = ?1", model.getStateId())
.map(reqs -> reqs.stream().map(req -> .map(reqs -> reqs.stream().map(req ->
new SimpleAffiliation(req.getId() * -1, model.getId(), req.getSaison(), false))) new SimpleAffiliation(req.getId() * -1, model.getStateId(), req.getSaison(), false)))
.map(aff2 -> Stream.concat(aff2, .map(aff2 -> Stream.concat(aff2,
model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList()) model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList())
); );
@@ -411,9 +424,9 @@ public class AffiliationService {
return Panache.withTransaction(() -> repository.deleteById(id)); return Panache.withTransaction(() -> repository.deleteById(id));
} }
public Uni<?> deleteReqAffiliation(long id, String reason) { public Uni<?> deleteReqAffiliation(long id, String reason, boolean federationAdmin) {
return repositoryRequest.findById(id) return repositoryRequest.findById(id)
.call(aff -> reactiveMailer.send( .call(aff -> federationAdmin ? reactiveMailer.send(
Mail.withText(aff.getM1_email(), Mail.withText(aff.getM1_email(),
"FFSAF - Votre demande d'affiliation a été rejetée.", "FFSAF - Votre demande d'affiliation a été rejetée.",
String.format( String.format(
@@ -430,7 +443,7 @@ public class AffiliationService {
""", aff.getName(), reason) """, aff.getName(), reason)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr") ).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(aff.getM2_email(), aff.getM3_email()) .addTo(aff.getM2_email(), aff.getM3_email())
)) ) : Uni.createFrom().nullItem())
.chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff))) .chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff)))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/logo")) .call(__ -> Utils.deleteMedia(id, media, "aff_request/logo"))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/status")); .call(__ -> Utils.deleteMedia(id, media, "aff_request/status"));

View File

@@ -211,11 +211,9 @@ public class ClubService {
m.setTraining_day_time(input.getTraining_day_time()); m.setTraining_day_time(input.getTraining_day_time());
ls.logChange("Contact interne", m.getContact_intern(), input.getContact_intern(), m); ls.logChange("Contact interne", m.getContact_intern(), input.getContact_intern(), m);
m.setContact_intern(input.getContact_intern()); m.setContact_intern(input.getContact_intern());
ls.logChange("N° RNA", m.getRNA(), input.getRna(), m); if (input.getState_id() != null && !input.getState_id().isBlank()) {
m.setRNA(input.getRna()); ls.logChange("N° SIRET", m.getClubId(), input.getState_id(), m);
if (input.getSiret() != null && !input.getSiret().isBlank()) { m.setStateId(input.getState_id());
ls.logChange("N° SIRET", m.getSIRET(), input.getSiret(), m);
m.setSIRET(Long.parseLong(input.getSiret()));
} }
ls.logChange("Adresse administrative", m.getAddress(), input.getAddress(), m); ls.logChange("Adresse administrative", m.getAddress(), input.getAddress(), m);
m.setAddress(input.getAddress()); m.setAddress(input.getAddress());
@@ -251,9 +249,8 @@ public class ClubService {
clubModel.setTraining_location(input.getTraining_location()); clubModel.setTraining_location(input.getTraining_location());
clubModel.setTraining_day_time(input.getTraining_day_time()); clubModel.setTraining_day_time(input.getTraining_day_time());
clubModel.setContact_intern(input.getContact_intern()); clubModel.setContact_intern(input.getContact_intern());
clubModel.setRNA(input.getRna()); if (input.getState_id() != null && !input.getState_id().isBlank())
if (input.getSiret() != null && !input.getSiret().isBlank()) clubModel.setStateId(input.getState_id());
clubModel.setSIRET(Long.parseLong(input.getSiret()));
clubModel.setAddress(input.getAddress()); clubModel.setAddress(input.getAddress());
try { try {
@@ -300,9 +297,9 @@ public class ClubService {
.call(clubModel -> Mutiny.fetch(clubModel.getAffiliations())) .call(clubModel -> Mutiny.fetch(clubModel.getAffiliations()))
.invoke(clubModel -> { .invoke(clubModel -> {
data.setName(clubModel.getName()); data.setName(clubModel.getName());
data.setSiret(clubModel.getSIRET()); data.setState_id(clubModel.getStateId());
data.setRna(clubModel.getRNA());
data.setAddress(clubModel.getAddress()); data.setAddress(clubModel.getAddress());
data.setContact(clubModel.getContact_intern());
data.setSaison( data.setSaison(
clubModel.getAffiliations().stream().max(Comparator.comparing(AffiliationModel::getSaison)) clubModel.getAffiliations().stream().max(Comparator.comparing(AffiliationModel::getSaison))
.map(AffiliationModel::getSaison).map(i -> Math.min(i + 1, Utils.getSaison() + 1)) .map(AffiliationModel::getSaison).map(i -> Math.min(i + 1, Utils.getSaison() + 1))

View File

@@ -231,9 +231,6 @@ public class KeycloakService {
user.setEmail(membreModel.getEmail()); user.setEmail(membreModel.getEmail());
user.setEnabled(true); user.setEnabled(true);
user.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
try (Response response = keycloak.realm(realm).users().create(user)) { try (Response response = keycloak.realm(realm).users().create(user)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo() if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo()
.equals(Response.Status.CONFLICT)) .equals(Response.Status.CONFLICT))
@@ -245,13 +242,6 @@ public class KeycloakService {
return getUser(login).orElseThrow( return getUser(login).orElseThrow(
() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin))); () -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
}) })
.call(user -> enabled_email ?
vertx.getOrCreateContext().executeBlocking(() -> {
keycloak.realm(realm).users().get(user.getId())
.executeActionsEmail(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
return null;
}) : Uni.createFrom().nullItem())
.invoke(user -> membreModel.setUserId(user.getId())) .invoke(user -> membreModel.setUserId(user.getId()))
.call(user -> updateRole(user.getId(), List.of("safca_user"), List.of())) .call(user -> updateRole(user.getId(), List.of("safca_user"), List.of()))
.call(user -> enabled_email ? reactiveMailer.send( .call(user -> enabled_email ? reactiveMailer.send(
@@ -261,14 +251,14 @@ public class KeycloakService {
""" """
Bonjour, Bonjour,
Suite à votre première inscription %sà la Fédération Française de Soft Armored Fighting (FFSAF), votre compte pour accéder à l'intranet a été créé. Suite à votre première inscription %sà la Fédération Française de Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
Ce compte vous permettra de consulter vos informations, de vous inscrire aux compétitions et de consulter vos résultats. Ce compte vous permettra de consulter vos informations et, dans un futur proche, de vous inscrire aux compétitions ainsi que d'en consulter les résultats.
Vous allez recevoir dans les prochaines minutes un email vous demandant de vérifier votre email et de définir un mot de passe.
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
Votre nom d'utilisateur est : %s Votre nom d'utilisateur est : %s
Pour définir votre mot de passe, rendez-vous sur l'intranet > "Connexion" > "Mot de passe oublié ?"
Si vous n'avez pas demandé cette inscription, veuillez contacter le support à l'adresse support@ffsaf.fr. Si vous n'avez pas demandé cette inscription, veuillez contacter le support à l'adresse support@ffsaf.fr.
(Pas de panique, nous ne vous enverrons pas de message autre que ce concernant votre compte) (Pas de panique, nous ne vous enverrons pas de message autre que ce concernant votre compte)

View File

@@ -18,6 +18,7 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -26,6 +27,7 @@ import java.util.function.Function;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class LicenceService { public class LicenceService {
private static final Logger LOGGER = Logger.getLogger(LicenceService.class);
@Inject @Inject
LicenceRepository repository; LicenceRepository repository;
@@ -125,7 +127,9 @@ public class LicenceService {
.chain(() -> combRepository.persist(membreModel)) .chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem()) : Uni.createFrom().nullItem())
.call(__ -> (membreModel.getUserId() == null) ? .call(__ -> (membreModel.getUserId() == null) ?
keycloakService.initCompte(membreModel.getId()) keycloakService.initCompte(membreModel.getId()).onFailure()
.invoke(t -> LOGGER.infof("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull()
: Uni.createFrom().nullItem()); : Uni.createFrom().nullItem());
} }

View File

@@ -197,6 +197,11 @@ public class MembreService {
return Uni.createFrom().nullItem(); return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>(); AtomicReference<ClubModel> clubModel = new AtomicReference<>();
LOGGER.debugf("Membre import (size=%d)", data2.size());
for (SimpleMembreInOutData simpleMembreInOutData : data2) {
LOGGER.debugf("-> %s", simpleMembreInOutData.toString());
}
return repository.find("userId = ?1", subject).firstResult() return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> { .chain(membreModel -> {
clubModel.set(membreModel.getClub()); clubModel.set(membreModel.getClub());
@@ -205,20 +210,24 @@ public class MembreService {
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3", return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(), data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(), data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
data2.stream().map(SimpleMembreInOutData::getEmail).filter(Objects::nonNull).toList()); data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank())
.toList());
}) })
.call(Unchecked.function(membres -> { .call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) { for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get())) if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
LOGGER.info("Similar membres found: " + membreModel);
throw new DForbiddenException( throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club"); "Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
} }
Uni<Void> uniResult = Uni.createFrom().voidItem(); Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) { for (SimpleMembreInOutData dataIn : data2) {
MembreModel model = membres.stream() MembreModel model = membres.stream()
.filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname() .filter(m -> (dataIn.getLicence() != null && Objects.equals(m.getLicence(),
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom()) || dataIn.getLicence())) || m.getLname().equals(dataIn.getNom()) && m.getFname()
Objects.equals(m.getFname(), dataIn.getEmail())).findFirst() .equals(dataIn.getPrenom()) || (dataIn.getEmail() != null && !dataIn.getEmail()
.isBlank() && Objects.equals(m.getFname(), dataIn.getEmail()))).findFirst()
.orElseGet(() -> { .orElseGet(() -> {
MembreModel mm = new MembreModel(); MembreModel mm = new MembreModel();
mm.setClub(clubModel.get()); mm.setClub(clubModel.get());
@@ -226,16 +235,23 @@ public class MembreService {
mm.setCountry("FR"); mm.setCountry("FR");
return mm; return mm;
}); });
if (model.getId() != null) {
LOGGER.debugf("updating -> %s", dataIn.toString());
} else {
LOGGER.debugf("creating -> %s", dataIn.toString());
}
if (model.getEmail() != null) { if (model.getEmail() != null && !model.getEmail().isBlank()) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) { if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
throw new DBadRequestException("Email déja utiliser"); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
} }
if (StringSimilarity.similarity(model.getLname().toUpperCase(), if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity( dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
throw new DBadRequestException("Email déja utiliser"); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
} }
} }
@@ -244,6 +260,7 @@ public class MembreService {
if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(), if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity( dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException( throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide."); "Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
} }
@@ -319,7 +336,7 @@ public class MembreService {
return update(repository.findById(id) return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id) .call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> { .invoke(Unchecked.consumer(c -> {
if (c > 0) if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser"); throw new DBadRequestException("Email déjà utiliser");
}))) })))
.chain(membreModel -> clubRepository.findById(membre.getClub()) .chain(membreModel -> clubRepository.findById(membre.getClub())
@@ -341,7 +358,7 @@ public class MembreService {
return update(repository.findById(id) return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id) .call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> { .invoke(Unchecked.consumer(c -> {
if (c > 0) if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser"); throw new DBadRequestException("Email déjà utiliser");
}))) })))
.invoke(Unchecked.consumer(membreModel -> { .invoke(Unchecked.consumer(membreModel -> {

View File

@@ -92,7 +92,7 @@ public class AffiliationRequestEndpoints {
@DELETE @DELETE
@Path("/{id}") @Path("/{id}")
@RolesAllowed({"federation_admin"}) @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " + @Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " +
"d'affiliation pour l'identifiant spécifié.") "d'affiliation pour l'identifiant spécifié.")
@@ -107,7 +107,7 @@ public class AffiliationRequestEndpoints {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin")) if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException(); throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub())) })).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id, reason)); .chain(o -> service.deleteReqAffiliation(id, reason, securityCtx.roleHas("federation_admin")));
} }
@PUT @PUT

View File

@@ -1,7 +1,7 @@
package fr.titionfire.ffsaf.rest; package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.rest.client.SirenService; import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot; import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException; import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
@@ -13,17 +13,19 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
public class AssoEndpoints { public class AssoEndpoints {
@RestClient @RestClient
SirenService sirenService; StateIdService stateIdService;
@GET @GET
@Path("siren/{siren}") @Path("state_id/{stateId}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true) @Operation(hidden = true)
public Uni<UniteLegaleRoot> getInfoSiren(@PathParam("siren") String siren) { public Uni<AssoData> getAssoInfo(@PathParam("stateId") String stateId) {
return sirenService.get_unite(siren).onFailure().transform(throwable -> { return stateIdService.get_status(stateId).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) { if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 404)
return new DNotFoundException("Service momentanément indisponible");
if (exception.getResponse().getStatus() == 400) if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Siret introuvable"); return new DNotFoundException("Asso introuvable");
} }
return throwable; return throwable;
}); });

View File

@@ -29,6 +29,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -69,7 +70,8 @@ public class ClubEndpoints {
@APIResponse(responseCode = "500", description = "Erreur interne du serveur") @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
}) })
public Uni<List<SimpleClubModel>> getAll() { public Uni<List<SimpleClubModel>> getAll() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList()); return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted(
Comparator.comparing(SimpleClubModel::getName)).toList());
} }
@GET @GET

View File

@@ -1,19 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
@ClientHeaderParam(name = "X-Client-Secret", value = "${siren-api.key}")
public interface SirenService {
@GET
@Path("/v3/unites_legales/{SIREN}")
Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren);
}

View File

@@ -0,0 +1,19 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.AssoData;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
public interface StateIdService {
@GET
@Path("/structure/{id}")
@CacheResult(cacheName = "AssoData_status")
Uni<AssoData> get_status(@PathParam("id") String id);
}

View File

@@ -0,0 +1,38 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
@Data
@RegisterForReflection
public class AssoData {
String id_siren;
String id_rna;
Identite identite;
Coordonnee coordonnees;
@Data
@RegisterForReflection
public static class Identite {
String nom;
}
@Data
@RegisterForReflection
public static class Coordonnee {
Address adresse_siege;
}
@Data
@RegisterForReflection
public static class Address {
String cplt_1;
String cplt_2;
String cplt_3;
String num_voie;
String type_voie;
String voie;
String cp;
String commune;
}
}

View File

@@ -17,9 +17,9 @@ import java.util.List;
@RegisterForReflection @RegisterForReflection
public class RenewAffData { public class RenewAffData {
String name; String name;
Long siret; String state_id;
String rna;
String address; String address;
String contact;
int saison; int saison;
List<RenewMember> members; List<RenewMember> members;

View File

@@ -14,8 +14,8 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class SimpleAffiliation { public class SimpleAffiliation {
@Schema(description = "L'identifiant de l'affiliation.", example = "1") @Schema(description = "L'identifiant de l'affiliation.", example = "1")
private Long id; private Long id;
@Schema(description = "L'identifiant du club associé à l'affiliation.", example = "123") @Schema(description = "L'identifiant du club associé à l'affiliation si id > 0 sinon n° SIRET ou RNA du club.", example = "123")
private Long club; private String club;
@Schema(description = "La saison de l'affiliation.", example = "2022") @Schema(description = "La saison de l'affiliation.", example = "2022")
private int saison; private int saison;
@Schema(description = "Indique si l'affiliation est validée ou non.", example = "true") @Schema(description = "Indique si l'affiliation est validée ou non.", example = "true")
@@ -27,7 +27,7 @@ public class SimpleAffiliation {
return new SimpleAffiliationBuilder() return new SimpleAffiliationBuilder()
.id(model.getId()) .id(model.getId())
.club(model.getClub().getId()) .club(String.valueOf(model.getClub().getId()))
.saison(model.getSaison()) .saison(model.getSaison())
.validate(true) .validate(true)
.build(); .build();

View File

@@ -36,10 +36,8 @@ public class SimpleClub {
private String contact_intern; private String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris") @Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
private String address; private String address;
@Schema(description = "RNA du club", example = "W123456789") @Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
private String RNA; private String state_id;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
private Long SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345") @Schema(description = "Numéro d'affiliation du club", example = "12345")
private Long no_affiliation; private Long no_affiliation;
@Schema(description = "Club international", example = "false") @Schema(description = "Club international", example = "false")
@@ -60,8 +58,7 @@ public class SimpleClub {
.training_location(model.getTraining_location()) .training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time()) .training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern()) .contact_intern(model.getContact_intern())
.RNA(model.getRNA()) .state_id(model.getStateId())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation()) .no_affiliation(model.getNo_affiliation())
.international(model.isInternational()) .international(model.isInternational())
.address(model.getAddress()) .address(model.getAddress())

View File

@@ -20,8 +20,8 @@ public class SimpleClubList {
String name; String name;
@Schema(description = "Pays du club", example = "FR") @Schema(description = "Pays du club", example = "FR")
String country; String country;
@Schema(description = "Numéro SIRET du club", example = "12345678901234") @Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
Long siret; String state_id;
@Schema(description = "Numéro d'affiliation du club", example = "12345") @Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation; Long no_affiliation;
@@ -29,7 +29,7 @@ public class SimpleClubList {
if (model == null) if (model == null)
return null; return null;
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getSIRET(), return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getStateId(),
model.getNo_affiliation()); model.getNo_affiliation());
} }
} }

View File

@@ -25,10 +25,8 @@ public class SimpleReqAffiliation {
Long club_no_aff; Long club_no_aff;
@Schema(description = "Nom du club demander", example = "Association sportive") @Schema(description = "Nom du club demander", example = "Association sportive")
String name; String name;
@Schema(description = "Numéro SIRET de l'association", example = "12345678901234") @Schema(description = "Numéro SIRET ou RNA de l'association", example = "12345678901234")
long siret; String stateId;
@Schema(description = "Numéro RNA de l'association", example = "W123456789")
String RNA;
@Schema(description = "Adresse de l'association", example = "1 rue de l'exemple, 75000 Paris") @Schema(description = "Adresse de l'association", example = "1 rue de l'exemple, 75000 Paris")
String address; String address;
@Schema(description = "Email de contact de l'association", example = "test@test.fr") @Schema(description = "Email de contact de l'association", example = "test@test.fr")
@@ -45,8 +43,7 @@ public class SimpleReqAffiliation {
return new SimpleReqAffiliation.SimpleReqAffiliationBuilder() return new SimpleReqAffiliation.SimpleReqAffiliationBuilder()
.id(model.getId()) .id(model.getId())
.name(model.getName()) .name(model.getName())
.siret(model.getSiret()) .stateId(model.getState_id())
.RNA(model.getRNA())
.address(model.getAddress()) .address(model.getAddress())
.saison(model.getSaison()) .saison(model.getSaison())
.contact(model.getContact()) .contact(model.getContact())

View File

@@ -16,8 +16,8 @@ public class SimpleReqAffiliationResume {
Long id; Long id;
@Schema(description = "Le nom de l'association.", example = "Association sportive") @Schema(description = "Le nom de l'association.", example = "Association sportive")
String name; String name;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234") @Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234")
long siret; String stateId;
@Schema(description = "La saison de l'affiliation.", example = "2025") @Schema(description = "La saison de l'affiliation.", example = "2025")
int saison; int saison;
@@ -25,10 +25,10 @@ public class SimpleReqAffiliationResume {
if (model == null) if (model == null)
return null; return null;
return new SimpleReqAffiliationResume.SimpleReqAffiliationResumeBuilder() return new SimpleReqAffiliationResumeBuilder()
.id(model.getId()) .id(model.getId())
.name(model.getName()) .name(model.getName())
.siret(model.getSiret()) .stateId(model.getState_id())
.saison(model.getSaison()) .saison(model.getSaison())
.build(); .build();
} }

View File

@@ -1,107 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
@Data
@RegisterForReflection
public class UniteLegaleRoot {
public UniteLegale unite_legale;
@Data
@RegisterForReflection
public static class UniteLegale {
public String activite_principale;
public Object annee_categorie_entreprise;
public Object annee_effectifs;
public Object caractere_employeur;
public Object categorie_entreprise;
public String categorie_juridique;
public String date_creation;
public String date_debut;
public Date date_dernier_traitement;
public String denomination;
public Object denomination_usuelle_1;
public Object denomination_usuelle_2;
public Object denomination_usuelle_3;
public String economie_sociale_solidaire;
public Etablissement etablissement_siege;
public ArrayList<Etablissement> etablissements;
public String etat_administratif;
public String identifiant_association;
public String nic_siege;
public Object nom;
public Object nom_usage;
public int nombre_periodes;
public String nomenclature_activite_principale;
public Object prenom_1;
public Object prenom_2;
public Object prenom_3;
public Object prenom_4;
public Object prenom_usuel;
public Object pseudonyme;
public Object sexe;
public Object sigle;
public String siren;
public String societe_mission;
public String statut_diffusion;
public Object tranche_effectifs;
public Object unite_purgee;
}
@Data
@RegisterForReflection
public static class Etablissement {
private String activite_principale;
private Object activite_principale_registre_metiers;
private Object annee_effectifs;
private String caractere_employeur;
private Object code_cedex;
private Object code_cedex_2;
private String code_commune;
private Object code_commune_2;
private Object code_pays_etranger;
private Object code_pays_etranger_2;
private String code_postal;
private Object code_postal_2;
private Object complement_adresse;
private Object complement_adresse2;
private String date_creation;
private String date_debut;
private Date date_dernier_traitement;
private Object denomination_usuelle;
private Object distribution_speciale;
private Object distribution_speciale_2;
private Object enseigne_1;
private Object enseigne_2;
private Object enseigne_3;
private boolean etablissement_siege;
private String etat_administratif;
private Object indice_repetition;
private Object indice_repetition_2;
private Object libelle_cedex;
private Object libelle_cedex_2;
private String libelle_commune;
private Object libelle_commune_2;
private Object libelle_commune_etranger;
private Object libelle_commune_etranger_2;
private Object libelle_pays_etranger;
private Object libelle_pays_etranger_2;
private String libelle_voie;
private Object libelle_voie_2;
private String nic;
private int nombre_periodes;
private String nomenclature_activite_principale;
private String numero_voie;
private Object numero_voie_2;
private String siren;
private String siret;
private String statut_diffusion;
private Object tranche_effectifs;
private String type_voie;
private Object type_voie_2;
}
}

View File

@@ -11,7 +11,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType; import org.jboss.resteasy.reactive.PartType;
@Getter @Getter
@ToString @ToString(exclude = {"status", "logo"})
public class AffiliationRequestForm { public class AffiliationRequestForm {
@Schema(description = "L'identifiant de l'affiliation. (null si nouvelle demande d'affiliation)") @Schema(description = "L'identifiant de l'affiliation. (null si nouvelle demande d'affiliation)")
@FormParam("id") @FormParam("id")
@@ -21,13 +21,9 @@ public class AffiliationRequestForm {
@FormParam("name") @FormParam("name")
private String name = null; private String name = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true) @Schema(description = "Le numéro SIRET/RNA de l'association.", example = "12345678901234", required = true)
@FormParam("siret") @FormParam("state_id")
private Long siret = null; private String state_id = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true) @Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("adresse") @FormParam("adresse")
@@ -114,8 +110,7 @@ public class AffiliationRequestForm {
public AffiliationRequestModel toModel() { public AffiliationRequestModel toModel() {
AffiliationRequestModel model = new AffiliationRequestModel(); AffiliationRequestModel model = new AffiliationRequestModel();
model.setName(this.getName()); model.setName(this.getName());
model.setSiret(this.getSiret()); model.setState_id(this.getState_id());
model.setRNA(this.getRna());
model.setAddress(this.getAdresse()); model.setAddress(this.getAdresse());
model.setSaison(this.getSaison()); model.setSaison(this.getSaison());
model.setContact(this.getContact()); model.setContact(this.getContact());

View File

@@ -4,12 +4,10 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import jakarta.ws.rs.FormParam; import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType; import org.jboss.resteasy.reactive.PartType;
@Getter @Getter
@ToString
public class AffiliationRequestSaveForm { public class AffiliationRequestSaveForm {
@Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true) @Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true)
@FormParam("id") @FormParam("id")
@@ -19,13 +17,9 @@ public class AffiliationRequestSaveForm {
@FormParam("name") @FormParam("name")
private String name = null; private String name = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true) @Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234", required = true)
@FormParam("siret") @FormParam("state_id")
private Long siret = null; private String state_id = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true) @Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("address") @FormParam("address")
@@ -171,4 +165,38 @@ public class AffiliationRequestSaveForm {
} }
} }
} }
@Override
public String toString() {
return "AffiliationRequestSaveForm{" +
"id=" + id +
", name='" + name + '\'' +
", state_id=" + state_id +
", address='" + address + '\'' +
", contact='" + contact + '\'' +
", status_len=" + status.length +
", logo_len=" + logo.length +
", m1_mode=" + m1_mode +
", m1_role=" + m1_role +
", m1_lincence='" + m1_lincence + '\'' +
", m1_lname='" + m1_lname + '\'' +
", m1_fname='" + m1_fname + '\'' +
", m1_email='" + m1_email + '\'' +
", m1_email_mode=" + m1_email_mode +
", m2_mode=" + m2_mode +
", m2_role=" + m2_role +
", m2_lincence='" + m2_lincence + '\'' +
", m2_lname='" + m2_lname + '\'' +
", m2_fname='" + m2_fname + '\'' +
", m2_email='" + m2_email + '\'' +
", m2_email_mode=" + m2_email_mode +
", m3_mode=" + m3_mode +
", m3_role=" + m3_role +
", m3_lincence='" + m3_lincence + '\'' +
", m3_lname='" + m3_lname + '\'' +
", m3_fname='" + m3_fname + '\'' +
", m3_email='" + m3_email + '\'' +
", m3_email_mode=" + m3_email_mode +
'}';
}
} }

View File

@@ -43,13 +43,9 @@ public class FullClubForm {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris", required = true) @Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris", required = true)
private String address = null; private String address = null;
@FormParam("rna") @FormParam("state_id")
@Schema(description = "RNA du club", example = "W123456789") @Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234", required = true)
private String rna = null; private String state_id = null;
@FormParam("siret")
@Schema(description = "Numéro SIRET du club", example = "12345678901234", required = true)
private String siret = null;
@FormParam("international") @FormParam("international")
@Schema(description = "Club international", example = "false", required = true) @Schema(description = "Club international", example = "false", required = true)

View File

@@ -41,8 +41,7 @@ database.pass=
notif.affRequest.mail= notif.affRequest.mail=
siren-api.key=siren-ap quarkus.rest-client."fr.titionfire.ffsaf.rest.client.StateIdService".url=https://siva-int.menjes.ate.info/apim/api-asso/api/
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.SirenService".url=https://data.siren-api.fr/
#Login #Login
quarkus.oidc.token-state-manager.split-tokens=true quarkus.oidc.token-state-manager.split-tokens=true

View File

@@ -1,11 +1,11 @@
import {useEffect, useRef} from 'react' import {useEffect, useRef} from 'react'
import {Nav} from "./components/Nav.jsx"; import {Nav} from "./components/Nav.jsx";
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom"; import {createBrowserRouter, Outlet, RouterProvider, useLocation, useRouteError} from "react-router-dom";
import {Home} from "./pages/Homepage.jsx"; import {Home} from "./pages/Homepage.jsx";
import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx"; import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
import {AuthCallback} from "./components/auhCallback.jsx"; import {AuthCallback} from "./components/auhCallback.jsx";
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx"; import {KeycloakContextProvider, useAuth, useAuthDispatch} from "./hooks/useAuth.jsx";
import {check_validity} from "./utils/auth.js"; import {check_validity, login} from "./utils/auth.js";
import {ToastContainer} from "react-toastify"; import {ToastContainer} from "react-toastify";
import './App.css' import './App.css'
@@ -14,6 +14,7 @@ import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx";
import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx"; import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
import {MePage} from "./pages/MePage.jsx"; import {MePage} from "./pages/MePage.jsx";
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx"; import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
import {FallingLines} from "react-loader-spinner";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@@ -113,6 +114,43 @@ function Root() {
theme="light" theme="light"
transition: Flip transition: Flip
/> />
<ReAuthMsg/>
</div>
</>
}
function ReAuthMsg() {
const {is_authenticated} = useAuth()
const location = useLocation()
const notAuthPaths = [
/^\/$/s,
/^\/affiliation(\/)?$/s,
/^\/affiliation\/ok(\/)?$/s,
/^\/complete\/auth.*$/s
]
if (is_authenticated || notAuthPaths.some(r => r.test(location.pathname)))
return <></>
return <>
<div className="overlayBG" style={{position: 'fixed'}}>
<div className="overlayContent" onClick={(e) => {
e.stopPropagation()
}}>
<div className="card">
<div className="card-header">
<h5>Session expirée</h5>
</div>
<div className="card-body">
<p className="card-text">Votre session a expirée, veuillez vous reconnecter pour continuer à
utiliser l'application.</p>
</div>
<div className="card-footer">
<button className="btn btn-primary" onClick={() => login()} style={{marginRight: "0.5em"}}>Se reconnecter</button>
<a className="btn btn-secondary" href="/">Accueil</a>
</div>
</div>
</div>
</div> </div>
</> </>
} }

View File

@@ -38,7 +38,7 @@ export function HoraireEditor({data}) {
return <div className="row mb-3"> return <div className="row mb-3">
<input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/> <input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Horaires d'entrainements</span> <span className="input-group-text">Horaires d'entraînements</span>
<ul className="list-group form-control"> <ul className="list-group form-control">
{state.map((d, index) => { {state.map((d, index) => {
return <div key={index} className="input-group"> return <div key={index} className="input-group">
@@ -92,4 +92,4 @@ export function HoraireEditor({data}) {
</div> </div>
</ul> </ul>
</div> </div>
} }

View File

@@ -42,7 +42,7 @@ export function LocationEditor({data, setModal, sendData}) {
return <div className="row mb-3"> return <div className="row mb-3">
<input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/> <input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Lieux d'entrainements</span> <span className="input-group-text">Lieux d'entraînements</span>
<ul className="list-group form-control"> <ul className="list-group form-control">
{state.map((d, index) => { {state.map((d, index) => {
return <div key={index} className="input-group"> return <div key={index} className="input-group">

View File

@@ -88,13 +88,14 @@ export function CountryList({name, text, value, values = undefined, disabled = f
</div> </div>
} }
export function TextField({name, text, value, placeholder, type = "text", disabled = false, required = true}) { export function TextField({name, text, value, placeholder, type = "text", disabled = false, required = true, ttip = null}) {
return <div className="row"> return <div className="row mb-3">
<div className="input-group mb-3"> <div className="input-group">
<span className="input-group-text" id={name}>{text}</span> <span className="input-group-text" id={name}>{text}</span>
<input type={type} className="form-control" placeholder={placeholder ? placeholder : text} aria-label={name} <input type={type} className="form-control" placeholder={placeholder ? placeholder : text} aria-label={name}
name={name} aria-describedby={name} defaultValue={value} disabled={disabled} required={required}/> name={name} aria-describedby={name} defaultValue={value} disabled={disabled} required={required}/>
</div> </div>
{ttip}
</div> </div>
} }

View File

@@ -54,7 +54,7 @@ function ClubMenu() {
</div> </div>
<ul className="dropdown-menu"> <ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/club/me">Mon club</NavLink></li> <li className="nav-item"><NavLink className="nav-link" to="/club/me">Mon club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/club/member">Member</NavLink></li> <li className="nav-item"><NavLink className="nav-link" to="/club/member">Membres</NavLink></li>
</ul> </ul>
</li> </li>
} }
@@ -70,7 +70,7 @@ function AdminMenu() {
Administration Administration
</div> </div>
<ul className="dropdown-menu"> <ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Member</NavLink></li> <li className="nav-item"><NavLink className="nav-link" to="/admin/member">Membres</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/club">Club</NavLink></li> <li className="nav-item"><NavLink className="nav-link" to="/admin/club">Club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/stats">Statistiques</NavLink></li> <li className="nav-item"><NavLink className="nav-link" to="/admin/stats">Statistiques</NavLink></li>
</ul> </ul>
@@ -99,4 +99,4 @@ function LoginMenu() {
</li> </li>
} }
</> </>
} }

View File

@@ -16,28 +16,26 @@ function formatAdresse(data) {
}).join(" "); }).join(" ");
} }
function reconstruireAdresse(infos) { function reconstruireAdresse2(infos) {
console.log(infos);
let adresseReconstruite = ""; let adresseReconstruite = "";
if(infos.numero_voie === null){ if (infos?.cplt_1) {
if (infos.complement_adresse) { adresseReconstruite += formatAdresse(infos.cplt_1) + ', ';
adresseReconstruite += formatAdresse(infos.complement_adresse) + ', '; }
} if (infos?.cplt_2) {
}else{ adresseReconstruite += formatAdresse(infos.cplt_2) + ', ';
adresseReconstruite += infos.numero_voie + ' '; }
if (infos?.cplt_3) {
adresseReconstruite += formatAdresse(infos.cplt_3) + ', ';
}
if (infos?.num_voie) {
adresseReconstruite += infos.num_voie + ' ';
} }
adresseReconstruite += formatAdresse(infos.type_voie) + ' '; adresseReconstruite += formatAdresse(infos.type_voie) + ' ';
adresseReconstruite += formatAdresse(infos.libelle_voie) + ', '; adresseReconstruite += formatAdresse(infos.voie) + ', ';
adresseReconstruite += infos.code_postal + ' ' + infos.libelle_commune + ', '; adresseReconstruite += infos.cp + ' ' + infos.commune + ', ';
if (infos.complement_adresse && infos.numero_voie !== null) {
adresseReconstruite += formatAdresse(infos.complement_adresse) + ', ';
}
if (infos.code_cedex && infos.libelle_cedex) {
adresseReconstruite += 'Cedex ' + infos.code_cedex + ' - ' + infos.libelle_cedex;
}
if (adresseReconstruite.endsWith(', ')) { if (adresseReconstruite.endsWith(', ')) {
adresseReconstruite = adresseReconstruite.slice(0, -2); adresseReconstruite = adresseReconstruite.slice(0, -2);
@@ -46,6 +44,13 @@ function reconstruireAdresse(infos) {
return adresseReconstruite; return adresseReconstruite;
} }
function getSaisonToAff(currentDate = new Date()) {
if (currentDate.getMonth() >= 7) { //aout et plus
return currentDate.getFullYear()
} else {
return currentDate.getFullYear() - 1
}
}
export function DemandeAff() { export function DemandeAff() {
const {hash} = useLocation(); const {hash} = useLocation();
@@ -78,8 +83,7 @@ export function DemandeAff() {
event.preventDefault() event.preventDefault()
const formData = new FormData(event.target) const formData = new FormData(event.target)
formData.append("m1_role", event.target.m1_role?.value) formData.append("m1_role", event.target.m1_role?.value)
formData.append("rna", event.target.rna?.value) formData.append("state_id", event.target.state_id?.value)
formData.append("siret", event.target.siret?.value)
let error = false; let error = false;
for (let i = 1; i <= 3; i++) { for (let i = 1; i <= 3; i++) {
@@ -145,7 +149,7 @@ export function DemandeAff() {
} }
return <div> return <div>
<h1>Demande d'affiliation</h1> <h1>Demande d'affiliation {getSaisonToAff() + "-" + (getSaisonToAff() + 1)}</h1>
<p>L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de lannée <p>L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de lannée
suivante.</p> suivante.</p>
Pour saffilier, une association sportive doit réunir les conditions suivantes : Pour saffilier, une association sportive doit réunir les conditions suivantes :
@@ -212,21 +216,19 @@ export function DemandeAff() {
function AssoInfo({initData, needFile}) { function AssoInfo({initData, needFile}) {
const [denomination, setDenomination] = useState("") const [denomination, setDenomination] = useState("")
const [siret, setSiret] = useState(initData.siret ? String(initData.siret) : "") const [stateId, setStateId] = useState(initData.stateId ? String(initData.stateId) : (initData.state_id ? String(initData.state_id) : ""))
const [rna, setRna] = useState(initData.rna ? initData.rna : "")
const [rnaEnable, setRnaEnable] = useState(false)
const [adresse, setAdresse] = useState(initData.address ? initData.address : "") const [adresse, setAdresse] = useState(initData.address ? initData.address : "")
const [saison, setSaison] = useState(initData.saison ? initData.saison : getSaison())
const [contact, setContact] = useState(initData.contact ? initData.contact : "") const [contact, setContact] = useState(initData.contact ? initData.contact : "")
const fetchSiret = () => { const fetchStateId = () => {
if (siret.length < 14) { const regex = /^(?:\d{14}|W?\d{9})$/;
toast.error("Le SIRET doit contenir 14 chiffres") if (!regex.test(stateId)) {
toast.error("Le format du SIRET/RNA est invalide");
return; return;
} }
toast.promise( toast.promise(
apiAxios.get(`asso/siren/${siret.substring(0, siret.length - 5)}`), apiAxios.get(`asso/state_id/${stateId}`),
{ {
pending: "Recherche de l'association en cours", pending: "Recherche de l'association en cours",
success: "Association trouvée avec succès 🎉", success: "Association trouvée avec succès 🎉",
@@ -237,34 +239,14 @@ function AssoInfo({initData, needFile}) {
} }
} }
).then(data => { ).then(data => {
const data2 = data.data.unite_legale const data2 = data.data
setDenomination(data2.denomination) setDenomination(data2.identite.nom)
setRnaEnable(data2.identifiant_association === null)
setRna(data2.identifiant_association ? data2.identifiant_association : "")
if (!initData.saison || adresse === "") if (!initData.saison || adresse === "")
setAdresse(reconstruireAdresse(data2.etablissement_siege)) setAdresse(reconstruireAdresse2(data2.coordonnees.adresse_siege))
}) })
} }
const currentSaison = getSaison();
return <> return <>
<div className="input-group mb-3"> <input name="saison" value={getSaisonToAff()} readOnly hidden/>
<div className="input-group-text">
<input className="form-check-input mt-0" type="radio" value={currentSaison} aria-label={currentSaison + "-" + (currentSaison + 1)}
name={"saison"} checked={saison === currentSaison}
onChange={e => setSaison(Number(e.target.value))}/>
{currentSaison + "-" + (currentSaison + 1)}
</div>
<span className="input-group-text">OU</span>
<div className="input-group-text">
<input className="form-check-input mt-0" type="radio" value={currentSaison + 1}
aria-label={(currentSaison + 1) + "-" + (currentSaison + 2)}
name={"saison"} checked={saison === currentSaison + 1}
onChange={e => setSaison(Number(e.target.value))}/>
{(currentSaison + 1) + "-" + (currentSaison + 2)}
</div>
</div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<span className="input-group-text" id="basic-addon1">Nom de l'association*</span> <span className="input-group-text" id="basic-addon1">Nom de l'association*</span>
@@ -274,11 +256,11 @@ function AssoInfo({initData, needFile}) {
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<span className="input-group-text">N° SIRET*</span> <span className="input-group-text">N° SIRET ou RNA*</span>
<input type="number" className="form-control" placeholder="siret" name="siret" required value={siret} disabled={!needFile} <input type="text" className="form-control" placeholder="state_id" name="state_id" required value={stateId} disabled={!needFile}
onChange={e => setSiret(e.target.value)}/> onChange={e => setStateId(e.target.value)}/>
<button className="btn btn-outline-secondary" type="button" id="button-addon2" <button className="btn btn-outline-secondary" type="button" id="button-addon2"
onClick={fetchSiret}>Rechercher onClick={fetchStateId}>Rechercher
</button> </button>
</div> </div>
@@ -289,13 +271,6 @@ function AssoInfo({initData, needFile}) {
aria-describedby="basic-addon1" disabled value={denomination} readOnly/> aria-describedby="basic-addon1" disabled value={denomination} readOnly/>
</div> </div>
<div className="input-group mb-3">
<span className="input-group-text" id="basic-addon1">RNA</span>
<input type="text" className="form-control" placeholder="RNA" aria-label="RNA"
aria-describedby="basic-addon1"
disabled={!rnaEnable} name="rna" value={rna} onChange={e => setRna(e.target.value)}/>
</div>
<div className="mb-3"> <div className="mb-3">
<div className="input-group"> <div className="input-group">
<span className="input-group-text" id="basic-addon1">Adresse administrative*</span> <span className="input-group-text" id="basic-addon1">Adresse administrative*</span>

View File

@@ -5,22 +5,22 @@ export const Home = () => {
return <> return <>
<div className="container"> <div className="container">
<div style={{textAlign: "center", margin: "2em"}}> <div style={{textAlign: "center", margin: "2em"}}>
<h1 className="text-green-800 text-4xl">Bienvenu sur l'intranet de Fédération Française de Soft Armored Fighting</h1> <h1 className="text-green-800 text-4xl">Bienvenue sur lintranet de la Fédération France Soft Armored Fighting</h1>
</div> </div>
<div className="row" style={{marginTop: "3em"}}> <div className="row" style={{marginTop: "3em"}}>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}> <div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
<div className="align-content-center" <div className="align-content-center"
style={{textAlign: "center", backgroundColor: "#FFFFFF79", padding: "1em 1em 0em 1em", borderRadius: "3em 3em 0 0"}}> style={{textAlign: "center", backgroundColor: "#FFFFFF79", padding: "1em 1em 0em 1em", borderRadius: "3em 3em 0 0"}}>
<h2><FontAwesomeIcon icon={faUser} size="2xl"/></h2> <h2><FontAwesomeIcon icon={faUser} size="2xl"/></h2>
<h2>Pour les combatants</h2> <h2>Pour les licenciés</h2>
</div> </div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}> <p style={{padding: "0.5em 1em 0.5em 1em"}}>
Vous y retrouverez toutes vos informations ainsi que l'état de votre inscription à la fédération. Vous pouvez également Vous y retrouverez toutes vos informations ainsi que l'état de votre inscription à la fédération. Vous pouvez également
télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi qu'en consultée vos résultats sous réserve télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi que consulter vos résultats sous réserve que
que le club organisateur les ait renseignés. <br/> le club organisateur les ait renseignés. <br/>
<br/> <br/>
Lors de votre première inscription, vous réservez un email contenant vos Lors de votre première inscription, vous recevrez un email contenant vos informations d'identification, ce mail sera envoyé
informations d'identification sur ce site, ce mail sera envoyé une fois votre inscription validée par nos soins. une fois votre licence validée par le secrétariat.
</p> </p>
</div> </div>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}> <div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
@@ -30,12 +30,12 @@ export const Home = () => {
<h2>Pour les clubs</h2> <h2>Pour les clubs</h2>
</div> </div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}> <p style={{padding: "0.5em 1em 0.5em 1em"}}>
C'est ici que vous pouvez faire l'inscription de vos membres à la fédération, que vous pouvez demanderrenouveler votre C'est ici que vous pouvez prendre les licences fédérales pour vos adhérents, que vous pouvez demander ou renouveler votre
demande d'affiliation, renseigné vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur le affiliation, renseigner vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur
site ffsaf.fr.<br/> le site ffsaf.fr.<br/>
Vous aurez par ailleurs la possibilité de publier des formulaires d'inscriptions pour vos compétitions ainsi Vous aurez par ailleurs la possibilité de publier des formulaires d'inscriptions pour vos compétitions ainsi
que d'un publié les résultats.<br/><br/> que d'enregistrer les résultats.<br/><br/>
Vous n'étes pas encore affilié à la fédération ? Vous pouvez faire une demande d'affiliation en cliquant <a href="/affiliation">içi</a>. Vous n'êtes pas encore affilié à la fédération ? Cliquez <a href="/affiliation">içi</a> pour faire votre première demande.
</p> </p>
</div> </div>
</div> </div>
@@ -55,4 +55,4 @@ export const Home = () => {
}}> }}>
</div> </div>
</> </>
}; };

View File

@@ -68,7 +68,7 @@ function MakeRow({request, navigate}) {
<div className="ms-2 col-auto"> <div className="ms-2 col-auto">
<div className="fw-bold">{request.name}</div> <div className="fw-bold">{request.name}</div>
</div> </div>
<small style={{textAlign: 'right'}}>{request.saison}-{request.saison + 1}<br/>{request.siret}</small> <small style={{textAlign: 'right'}}>{request.saison}-{request.saison + 1}<br/>{request.state_id}</small>
</div> </div>
} }
@@ -104,4 +104,4 @@ function Def() {
<li className="list-group-item"><ThreeDots/></li> <li className="list-group-item"><ThreeDots/></li>
<li className="list-group-item"><ThreeDots/></li> <li className="list-group-item"><ThreeDots/></li>
</div> </div>
} }

View File

@@ -8,7 +8,6 @@ import {RoleList, TextField} from "../../../components/MemberCustomFiels.jsx";
import {useEffect, useRef, useState} from "react"; import {useEffect, useRef, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faFilePdf} from "@fortawesome/free-solid-svg-icons"; import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
const vite_url = import.meta.env.VITE_URL; const vite_url = import.meta.env.VITE_URL;
@@ -67,8 +66,7 @@ function Content({data, refresh}) {
formData.append('id', data.id); formData.append('id', data.id);
formData.append('name', event.target.name.value); formData.append('name', event.target.name.value);
formData.append('siret', event.target.siret.value); formData.append('state_id', event.target.state_id.value);
formData.append('rna', event.target.rna.value);
formData.append('address', event.target.address.value); formData.append('address', event.target.address.value);
formData.append('contact', event.target.contact.value); formData.append('contact', event.target.contact.value);
@@ -166,7 +164,7 @@ function Content({data, refresh}) {
<input name="id" value={data.id} readOnly hidden/> <input name="id" value={data.id} readOnly hidden/>
<div className="card-header">Demande d'affiliation</div> <div className="card-header">Demande d'affiliation</div>
<div className="card-body text-center"> <div className="card-body text-center">
{data.club && <h5>Ce club a déjà ete affilier (affiliation n°{data.club_no_aff})</h5>} {data.club && <h5>Ce club a déjà été affilié (affiliation n°{data.club_no_aff})</h5>}
<h4 id="saison">Saison {data.saison}-{data.saison + 1}</h4> <h4 id="saison">Saison {data.saison}-{data.saison + 1}</h4>
<div className="row mb-3"> <div className="row mb-3">
@@ -178,8 +176,7 @@ function Content({data, refresh}) {
{data.club && <div className="form-text" id="name">Ancien nom: {data.club_name}</div>} {data.club && <div className="form-text" id="name">Ancien nom: {data.club_name}</div>}
</div> </div>
<TextField type="number" name="siret" text="SIRET" value={data.siret} disabled={true}/> <TextField name="state_id" text="SIRET ou RNA" value={data.stateId} disabled={true}/>
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
<TextField name="address" text="Adresse" value={data.address}/> <TextField name="address" text="Adresse" value={data.address}/>
<TextField name="contact" text="Contact administratif" value={data.contact}/> <TextField name="contact" text="Contact administratif" value={data.contact}/>

View File

@@ -40,7 +40,7 @@ export function ClubList() {
country: e.country, country: e.country,
siret: e.siret, siret: e.siret,
no_affiliation: e.no_affiliation, no_affiliation: e.no_affiliation,
affiliation: showAffiliationState ? affiliationData.find(aff => (aff.id >= 0) ? aff.club === e.id : aff.club === e.siret) : null affiliation: showAffiliationState ? affiliationData.find(aff => (aff.id >= 0) ? Number(aff.club) === e.id : aff.club === e.state_id) : null
}) })
} }
setClubData(data2); setClubData(data2);
@@ -197,4 +197,4 @@ function Def() {
<li className="list-group-item"><ThreeDots/></li> <li className="list-group-item"><ThreeDots/></li>
<li className="list-group-item"><ThreeDots/></li> <li className="list-group-item"><ThreeDots/></li>
</div> </div>
} }

View File

@@ -130,8 +130,7 @@ function InformationForm({data}) {
</div> </div>
</div> </div>
{!switchOn && <> {!switchOn && <>
<TextField name="siret" text="SIRET" value={data.siret} required={false} type="number"/> <TextField name="state_id" text="SIRET ou RNA" value={data.state_id} required={false}/>
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false} <TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}
placeholder="example@test.com"/> placeholder="example@test.com"/>
<TextField name="address" text="Adresse administrative" value={data.address} required={false} <TextField name="address" text="Adresse administrative" value={data.address} required={false}
@@ -172,6 +171,7 @@ function InformationForm({data}) {
export function BureauCard({clubData}) { export function BureauCard({clubData}) {
const setLoading = useLoadingSwitcher() const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1) const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1)
const navigate = useNavigate();
return <> return <>
<div className="card mb-4"> <div className="card mb-4">
@@ -179,7 +179,8 @@ export function BureauCard({clubData}) {
<div className="card-body"> <div className="card-body">
<ul className="list-group"> <ul className="list-group">
{data && data.map((d, index) => { {data && data.map((d, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start"> return <div key={index} className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
onClick={__ => navigate(`/admin/member/${d.id}`)}>
<div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div> <div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div>
</div> </div>
})} })}
@@ -188,4 +189,4 @@ export function BureauCard({clubData}) {
</div> </div>
{error && <AxiosError error={error}/>} {error && <AxiosError error={error}/>}
</> </>
} }

View File

@@ -84,8 +84,7 @@ function InformationForm() {
</div> </div>
</div> </div>
{!switchOn && <> {!switchOn && <>
<TextField name="siret" text="SIRET" required={false} type="number"/> <TextField name="state_id" text="SIRET ou RNA" required={false}/>
<TextField name="rna" text="RNA" required={false}/>
<TextField name="contact_intern" text="Contact interne" required={false} placeholder="example@test.com"/> <TextField name="contact_intern" text="Contact interne" required={false} placeholder="example@test.com"/>
<TextField name="address" text="Adresse administrative" required={false} placeholder="Adresse administrative"/> <TextField name="address" text="Adresse administrative" required={false} placeholder="Adresse administrative"/>

View File

@@ -23,7 +23,7 @@ export function ClubRoot() {
return <> return <>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap'}}> <div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap'}}>
<h1>Espace club</h1><h3 style={{marginLeft: '0.75em'}}>{club}</h3></div> <h3 style={{marginLeft: '0.75em'}}>Club: {club}</h3></div>
<LoadingProvider> <LoadingProvider>
<Outlet/> <Outlet/>
</LoadingProvider> </LoadingProvider>

View File

@@ -1,12 +1,11 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js"; import {useFetch} from "../../../hooks/useFetch.js";
import {useEffect, useReducer, useState} from "react"; import {useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEye, faFilePdf, faPen} from "@fortawesome/free-solid-svg-icons"; import {faEye, faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx"; import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js"; import {apiAxios} from "../../../utils/Tools.js";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
const vite_url = import.meta.env.VITE_URL; const vite_url = import.meta.env.VITE_URL;
@@ -42,8 +41,8 @@ export function AffiliationCard({clubData}) {
<a href={`${vite_url}/api/club/me/affiliation`} target='#'> <a href={`${vite_url}/api/club/me/affiliation`} target='#'>
<button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}} <button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}}
onClick={e => null}> onClick={_ => null}>
Téléchargée l'attestation d'affiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon> Télécharger lattestation daffiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
</button> </button>
</a> </a>
</div> </div>
@@ -140,10 +139,10 @@ function ModalContent2({clubData, data}) {
} }
} }
if (list.length !== 3) { while (list.length < 3) {
toast.error("Il faut sélectionner 3 membres pour renouveler l'affiliation") list.push(-1)
return
} }
apiAxios.get(`/club/renew/${clubData.id}?m1=${list[0]}&m2=${list[1]}&m3=${list[2]}`).then(data => { apiAxios.get(`/club/renew/${clubData.id}?m1=${list[0]}&m2=${list[1]}&m3=${list[2]}`).then(data => {
navigate('/affiliation#d' + encodeURI(JSON.stringify(data.data))) navigate('/affiliation#d' + encodeURI(JSON.stringify(data.data)))
}) })

View File

@@ -1,9 +1,7 @@
import {useNavigate, useParams} from "react-router-dom";
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js"; import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import {apiAxios, errFormater} from "../../../utils/Tools.js"; import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx"; import {AxiosError} from "../../../components/AxiosError.jsx";
import {AffiliationCard, BureauCard} from "./AffiliationCard.jsx"; import {AffiliationCard, BureauCard} from "./AffiliationCard.jsx";
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx"; import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
@@ -22,7 +20,7 @@ export function MyClubPage() {
const {data, error} = useFetch(`/club/me`, setLoading, 1) const {data, error} = useFetch(`/club/me`, setLoading, 1)
return <> return <>
<h2>Mon club</h2> <h3>Données administratives</h3>
{data {data
? <div> ? <div>
<div className="row"> <div className="row">
@@ -77,8 +75,7 @@ function InformationForm({data}) {
<CountryList name="country" text="Pays" value={data.country} disabled={true}/> <CountryList name="country" text="Pays" value={data.country} disabled={true}/>
{!data.international && <> {!data.international && <>
<TextField name="siret" text="SIRET" value={data.siret} type="number" disabled={true}/> <TextField name="state_id" text="SIRET ou RNA" value={data.state_id} disabled={true}/>
<TextField name="rna" text="RNA" value={data.rna} required={false} disabled={true}/>
</>} </>}
<div className="row mb-3"> <div className="row mb-3">
@@ -91,7 +88,7 @@ function InformationForm({data}) {
<div className="col-md-6"> <div className="col-md-6">
<a href={`${vite_url}/api/club/${data.id}/status`} target='_blank'> <a href={`${vite_url}/api/club/${data.id}/status`} target='_blank'>
<button className="btn btn-outline-secondary" type="button" id="button-addon1" <button className="btn btn-outline-secondary" type="button" id="button-addon1"
onClick={e => null}> onClick={_ => null}>
<FontAwesomeIcon icon={faFilePdf} size="5x"></FontAwesomeIcon><br/> <FontAwesomeIcon icon={faFilePdf} size="5x"></FontAwesomeIcon><br/>
Voir les statues Voir les statues
</button> </button>

View File

@@ -49,7 +49,9 @@ export function InformationForm({data}) {
<TextField name="lname" text="Nom" value={data.lname}/> <TextField name="lname" text="Nom" value={data.lname}/>
<TextField name="fname" text="Prénom" value={data.fname}/> <TextField name="fname" text="Prénom" value={data.fname}/>
<TextField name="email" text="Email" value={data.email} placeholder="name@example.com" <TextField name="email" text="Email" value={data.email} placeholder="name@example.com"
type="email"/> type="email" ttip={<small className="form-text">L'email sert à la création de compte pour se connecter au site et doit être unique. <br/>
Pour les mineurs, l'email des parents peut être utilisé plusieurs fois grâce à la syntaxe suivante : {'email.parent+<caractères alphanumériques>@exemple.com'}.<br/>
Exemples : mail.parent+1@exemple.com, mail.parent+titouan@exemple.com, mail.parent+cedrique@exemple.com</small>}/>
<OptionField name="genre" text="Genre" value={data.genre} <OptionField name="genre" text="Genre" value={data.genre}
values={{NA: 'N/A', H: 'H', F: 'F'}}/> values={{NA: 'N/A', H: 'H', F: 'F'}}/>
<CountryList name="country" text="Pays" value={data.country}/> <CountryList name="country" text="Pays" value={data.country}/>

View File

@@ -9,8 +9,11 @@ export function check_validity(online_callback = () => {
axios.get(`${vite_url}/api/auth/userinfo`).then(data => { axios.get(`${vite_url}/api/auth/userinfo`).then(data => {
online_callback({state: true, userinfo: data.data}); online_callback({state: true, userinfo: data.data});
}) })
}else{
online_callback({state: false});
} }
}).catch(() => { }).catch(() => {
console.log("=> Not authenticated");
online_callback({state: false}); online_callback({state: false});
}) })
} }
@@ -32,4 +35,4 @@ export function login_redirect() {
export function logout() { export function logout() {
window.location.href = `${vite_url}/api/logout`; window.location.href = `${vite_url}/api/logout`;
} }

View File

@@ -24,4 +24,4 @@ export default ({mode}) => {
}, },
}, },
}); });
}; };