package fr.titionfire.ffsaf.domain.service; import fr.titionfire.ffsaf.data.model.*; import fr.titionfire.ffsaf.data.repository.*; import fr.titionfire.ffsaf.rest.data.SimpleAffiliation; import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation; import fr.titionfire.ffsaf.rest.exception.DBadRequestException; import fr.titionfire.ffsaf.rest.exception.DNotFoundException; import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm; import fr.titionfire.ffsaf.utils.Genre; import fr.titionfire.ffsaf.utils.GradeArbitrage; import fr.titionfire.ffsaf.utils.SequenceType; import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.mailer.Mail; import io.quarkus.mailer.reactive.ReactiveMailer; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.hibernate.reactive.mutiny.Mutiny; import org.jboss.logging.Logger; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @WithSession @ApplicationScoped public class AffiliationService { private static final Logger LOGGER = Logger.getLogger(AffiliationService.class); @Inject CombRepository combRepository; @Inject ClubRepository clubRepository; @Inject AffiliationRequestRepository repositoryRequest; @Inject AffiliationRepository repository; @Inject KeycloakService keycloakService; @Inject SequenceRepository sequenceRepository; @Inject LicenceRepository licenceRepository; @Inject ReactiveMailer reactiveMailer; @Inject LoggerService ls; @ConfigProperty(name = "upload_dir") String media; @ConfigProperty(name = "notif.affRequest.mail") List mails; public Uni> getAllReq() { return repositoryRequest.listAll(); } public Uni pre_save(AffiliationRequestForm form, boolean unique) { AffiliationRequestModel affModel = form.toModel(); int currentSaison = Utils.getSaison(); return Uni.createFrom().item(affModel) .invoke(Unchecked.consumer(model -> { if (model.getSaison() != currentSaison && model.getSaison() != currentSaison + 1) { throw new DBadRequestException("Saison non valid"); } })) .chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(), affModel.getSaison())) .onItem().invoke(Unchecked.consumer(count -> { if (count != 0 && unique) { throw new DBadRequestException("Demande d'affiliation déjà existante"); } })) .chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club -> repository.count("club = ?1 and saison = ?2", club, affModel.getSaison()))) .onItem().invoke(Unchecked.consumer(count -> { if (count != 0) { throw new DBadRequestException("Affiliation déjà existante"); } })) .map(o -> affModel) .call(model -> ((model.getM1_lincence() != -1) ? combRepository.find("licence", model.getM1_lincence()).count().invoke(Unchecked.consumer(count -> { if (count == 0) { throw new DBadRequestException("Licence membre n°1 inconnue"); } })) : Uni.createFrom().nullItem()) ) .call(model -> ((model.getM2_lincence() != -1) ? combRepository.find("licence", model.getM2_lincence()).count().invoke(Unchecked.consumer(count -> { if (count == 0) { throw new DBadRequestException("Licence membre n°2 inconnue"); } })) : Uni.createFrom().nullItem()) ) .call(model -> ((model.getM3_lincence() != -1) ? combRepository.find("licence", model.getM3_lincence()).count().invoke(Unchecked.consumer(count -> { if (count == 0) { throw new DBadRequestException("Licence membre n°3 inconnue"); } })) : Uni.createFrom().nullItem()) ); } public Uni saveEdit(AffiliationRequestForm form) { return pre_save(form, false) .chain(model -> repositoryRequest.findById(form.getId()) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .chain(origine -> { origine.setName(model.getName()); origine.setRNA(model.getRNA()); origine.setAddress(model.getAddress()); origine.setContact(model.getContact()); origine.setM1_lname(model.getM1_lname()); origine.setM1_fname(model.getM1_fname()); origine.setM1_lincence(model.getM1_lincence()); origine.setM1_role(model.getM1_role()); origine.setM1_email(model.getM1_email()); origine.setM2_lname(model.getM2_lname()); origine.setM2_fname(model.getM2_fname()); origine.setM2_lincence(model.getM2_lincence()); origine.setM2_role(model.getM2_role()); origine.setM2_email(model.getM2_email()); origine.setM3_lname(model.getM3_lname()); origine.setM3_fname(model.getM3_fname()); origine.setM3_lincence(model.getM3_lincence()); origine.setM3_role(model.getM3_role()); origine.setM3_email(model.getM3_email()); return Panache.withTransaction(() -> repositoryRequest.persist(origine)); })); } public Uni save(AffiliationRequestForm form) { LOGGER.debug("Affiliation Request Created"); LOGGER.debug(form.toString()); // noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher return pre_save(form, true) .chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model))) .onItem() .invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media, "aff_request/logo"))) .onItem() .invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media, "aff_request/status"))) .call(model -> reactiveMailer.send( Mail.withText("no-reply@ffsaf.fr", "[NOTIF] FFSAF - Nouvelle demande d'affiliation", String.format( """ Une nouvelle demande d'affiliation a été déposée sur l'intranet pour le club: %s. """, model.getName()) ).setFrom("FFSAF ") .addBcc(mails.toArray(String[]::new)) )) .map(__ -> "Ok"); } public Uni saveAdmin(AffiliationRequestSaveForm form) { LOGGER.debug("Affiliation Request Saved"); LOGGER.debug(form.toString()); return repositoryRequest.findById(form.getId()) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .map(model -> { model.setName(form.getName()); model.setSiret(form.getSiret()); model.setRNA(form.getRna()); model.setAddress(form.getAddress()); model.setContact(form.getContact()); if (form.getM1_mode() == 2) { model.setM1_lname(form.getM1_lname()); model.setM1_fname(form.getM1_fname()); } else { model.setM1_lincence( form.getM1_lincence() == null ? 0 : Integer.parseInt(form.getM1_lincence())); } model.setM1_role(form.getM1_role()); if (form.getM1_email_mode() == 0) model.setM1_email(form.getM1_email()); if (form.getM2_mode() == 2) { model.setM2_lname(form.getM2_lname()); model.setM2_fname(form.getM2_fname()); } else { model.setM2_lincence( form.getM2_lincence() == null ? 0 : Integer.parseInt(form.getM2_lincence())); } model.setM2_role(form.getM2_role()); if (form.getM2_email_mode() == 0) model.setM2_email(form.getM2_email()); if (form.getM3_mode() == 2) { model.setM3_lname(form.getM3_lname()); model.setM3_fname(form.getM3_fname()); } else { model.setM3_lincence( form.getM3_lincence() == null ? 0 : Integer.parseInt(form.getM3_lincence())); } model.setM3_role(form.getM3_role()); if (form.getM3_email_mode() == 0) model.setM3_email(form.getM3_email()); return model; }) .chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model))) .onItem() .invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media, "aff_request/logo"))) .onItem() .invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media, "aff_request/status"))) .map(__ -> "Ok"); } private Uni setMembre(AffiliationRequestSaveForm.Member member, ClubModel club, int saison) { return Uni.createFrom().nullItem().chain(__ -> { if (member.getMode() == 2) { MembreModel membreModel = new MembreModel(); membreModel.setFname(member.getFname()); membreModel.setLname(member.getLname().toUpperCase()); membreModel.setClub(club); membreModel.setRole(member.getRole()); membreModel.setEmail(member.getEmail()); membreModel.setGrade_arbitrage(GradeArbitrage.NA); membreModel.setGenre(Genre.NA); membreModel.setCountry("FR"); return Panache.withTransaction(() -> combRepository.persist(membreModel) .chain(m -> sequenceRepository.getNextValueInTransaction(SequenceType.Licence) .invoke(l -> m.setLicence(Math.toIntExact(l))) .chain(() -> combRepository.persist(m)))); } else { return combRepository.find("licence", Integer.parseInt(member.getLicence())).firstResult() .onItem().ifNull().switchTo(() -> { MembreModel membreModel = new MembreModel(); membreModel.setFname(member.getFname()); membreModel.setLname(member.getLname().toUpperCase()); return Panache.withTransaction( () -> sequenceRepository.getNextValueInTransaction(SequenceType.Licence) .invoke(l -> membreModel.setLicence(Math.toIntExact(l))) .chain(() -> combRepository.persist(membreModel))); }) .map(m -> { m.setClub(club); m.setRole(member.getRole()); m.setEmail(member.getEmail()); return m; }).call(m -> Panache.withTransaction(() -> combRepository.persist(m))); } }) .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())) .call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage())) .call(userId -> keycloakService.setEmail(userId, m.getEmail()))) .call(m -> Mutiny.fetch(m.getLicences()) .call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ? Uni.createFrom().nullItem() : Panache.withTransaction(() -> licenceRepository.persist( new LicenceModel(null, m, club.getId(), saison, null, true, false))) .call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(), licenceModel)))); } public Uni accept(AffiliationRequestSaveForm form) { LOGGER.debug("Affiliation Request Accepted"); LOGGER.debug(form.toString()); return repositoryRequest.findById(form.getId()) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .chain(req -> clubRepository.find("SIRET = ?1", form.getSiret()).firstResult() .chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model)) .call(club -> setMembre(form.new Member(1), club, req.getSaison()) .call(__ -> setMembre(form.new Member(2), club, req.getSaison()) .call(___ -> setMembre(form.new Member(3), club, req.getSaison())))) .onItem() .invoke(model -> Uni.createFrom() .future(Utils.replacePhoto(form.getId(), form.getLogo(), media, "aff_request/logo"))) .onItem() .invoke(model -> Uni.createFrom() .future(Utils.replacePhoto(form.getId(), form.getStatus(), media, "aff_request/status"))) .call(model -> Utils.moveMedia(form.getId(), model.getId(), media, "aff_request/logo", "ppClub")) .call(model -> Utils.moveMedia(form.getId(), model.getId(), media, "aff_request/status", "clubStatus")) ) .map(__ -> "Ok"); } private Uni acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) { LOGGER.debug("New Club Accepted"); return Uni.createFrom().nullItem() .chain(() -> { ClubModel club = new ClubModel(); club.setName(form.getName()); club.setCountry("FR"); club.setSIRET(form.getSiret()); club.setRNA(form.getRna()); club.setAddress(form.getAddress()); club.setContact_intern(form.getContact()); club.setAffiliations(new ArrayList<>()); return Panache.withTransaction(() -> clubRepository.persist(club) .chain(c -> sequenceRepository.getNextValueInTransaction(SequenceType.Affiliation) .invoke(c::setNo_affiliation) .chain(() -> clubRepository.persist(c)) .chain(() -> repositoryRequest.delete(model)) ) .chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison()))) .map(c -> club)); }) .call(club -> reactiveMailer.send( Mail.withText(form.getM1_email(), "FFSAF - Acceptation de votre demande d'affiliation", String.format( """ Bonjour, Votre demande d'affiliation pour le club %s a été acceptée. Le numéro d'affiliation de votre club est le %d. Cordialement, L'équipe de la FFSAF """, club.getName(), club.getNo_affiliation()) ).setFrom("FFSAF ").setReplyTo("contact@ffsaf.fr") .addTo(form.getM2_email(), form.getM3_email()) )); } private Uni acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) { LOGGER.debug("Old Club Accepted"); return Uni.createFrom().nullItem() .chain(() -> { club.setName(form.getName()); club.setCountry("FR"); club.setSIRET(form.getSiret()); club.setRNA(form.getRna()); club.setAddress(form.getAddress()); club.setContact_intern(form.getContact()); return Panache.withTransaction(() -> clubRepository.persist(club) .chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison()))) .chain(() -> repositoryRequest.delete(model))); }) .map(__ -> club); } public Uni getRequest(long id) { return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .call(out -> clubRepository.find("SIRET = ?1", out.getSiret()).firstResult().invoke(c -> { if (c != null) { out.setClub(c.getId()); out.setClub_name(c.getName()); out.setClub_no_aff(c.getNo_affiliation()); } }) ); } public Uni> getCurrentSaisonAffiliation() { return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison()) .map(models -> models.stream() .map(model -> new SimpleAffiliation(model.getId() * -1, model.getSiret(), model.getSaison(), false)).toList()) .chain(aff -> repository.list("saison = ?1", Utils.getSaison()) .map(models -> models.stream().map(SimpleAffiliation::fromModel).toList()) .map(aff2 -> Stream.concat(aff2.stream(), aff.stream()).toList()) ); } public Uni> getAffiliation(long id) { return clubRepository.findById(id) .onItem().ifNull().failWith(new DNotFoundException("Club non trouvé")) .call(model -> Mutiny.fetch(model.getAffiliations())) .chain(model -> repositoryRequest.list("siret = ?1", model.getSIRET()) .map(reqs -> reqs.stream().map(req -> new SimpleAffiliation(req.getId() * -1, model.getId(), req.getSaison(), false))) .map(aff2 -> Stream.concat(aff2, model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList()) ); } public Uni setAffiliation(long id, int saison) { return clubRepository.findById(id) .onItem().ifNull().failWith(new DNotFoundException("Club non trouvé")) .call(model -> Mutiny.fetch(model.getAffiliations())) .invoke(Unchecked.consumer(club -> { if (club.getAffiliations().stream().anyMatch(affiliation -> affiliation.getSaison() == saison)) { throw new DBadRequestException("Affiliation déjà existante"); } })) .chain(club -> Panache.withTransaction(() -> repository.persist(new AffiliationModel(null, club, saison)) .chain(c -> (club.getNo_affiliation() != null) ? Uni.createFrom().item(c) : sequenceRepository.getNextValueInTransaction(SequenceType.Affiliation) .invoke(club::setNo_affiliation) .chain(() -> clubRepository.persist(club)) .map(o -> c) ))) .map(SimpleAffiliation::fromModel); } public Uni deleteAffiliation(long id) { return Panache.withTransaction(() -> repository.deleteById(id)); } public Uni deleteReqAffiliation(long id, String reason) { return repositoryRequest.findById(id) .call(aff -> reactiveMailer.send( Mail.withText(aff.getM1_email(), "FFSAF - Votre demande d'affiliation a été rejetée.", String.format( """ Bonjour, Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s): %s Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse contact@ffsaf.fr. Cordialement, L'équipe de la FFSAF """, aff.getName(), reason) ).setFrom("FFSAF ").setReplyTo("contact@ffsaf.fr") .addTo(aff.getM2_email(), aff.getM3_email()) )) .chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff))) .call(__ -> Utils.deleteMedia(id, media, "aff_request/logo")) .call(__ -> Utils.deleteMedia(id, media, "aff_request/status")); } }