Merge pull request 'dev' (#61) from dev into master

Reviewed-on: #61
This commit is contained in:
Thibaut Valentin 2025-11-14 12:45:53 +00:00
commit f050127fd7
31 changed files with 173 additions and 300 deletions

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

@ -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

@ -80,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) {
@ -124,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());
@ -181,8 +180,7 @@ public class AffiliationService {
.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());
@ -289,10 +287,12 @@ public class AffiliationService {
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()
@ -317,8 +317,7 @@ public class AffiliationService {
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<>());
@ -355,8 +354,7 @@ public class AffiliationService {
.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)
@ -369,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());
@ -382,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())
@ -394,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())
); );
@ -426,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(
@ -445,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 % 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 % 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

@ -236,17 +236,17 @@ public class MembreService {
return mm; return mm;
}); });
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())) {
LOGGER.info("Similar membres found: " + model); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser"); 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) {
LOGGER.info("Similar membres found: " + model); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser"); throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
} }
} }

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

@ -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

@ -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

@ -17,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")
@ -175,8 +171,7 @@ public class AffiliationRequestSaveForm {
return "AffiliationRequestSaveForm{" + return "AffiliationRequestSaveForm{" +
"id=" + id + "id=" + id +
", name='" + name + '\'' + ", name='" + name + '\'' +
", siret=" + siret + ", state_id=" + state_id +
", rna='" + rna + '\'' +
", address='" + address + '\'' + ", address='" + address + '\'' +
", contact='" + contact + '\'' + ", contact='" + contact + '\'' +
", status_len=" + status.length + ", status_len=" + status.length +

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

@ -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);
@ -85,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++) {
@ -219,20 +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 [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 🎉",
@ -243,12 +239,10 @@ 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))
}) })
} }
return <> return <>
@ -262,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>
@ -277,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

@ -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

@ -66,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);
@ -177,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}

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

@ -75,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">

View File

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