feat: club membre import

This commit is contained in:
Thibaut Valentin 2025-07-02 18:13:34 +02:00
parent 17fea2272f
commit 5ce5df950f
4 changed files with 152 additions and 10 deletions

View File

@ -38,6 +38,7 @@ import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@WithSession
@ -138,6 +139,94 @@ public class MembreService {
.map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
}
/*
public Uni<List<SimpleMembre>> getSimilar(String fname, String lname) {
return repository.listAll().map(membreModels -> membreModels.stream()
.filter(m -> StringSimilarity.similarity(m.getFname(), fname) <= 3 &&
StringSimilarity.similarity(m.getLname(), lname) <= 3)
.map(SimpleMembre::fromModel).toList());
}+
*/
public Uni<String> allImporte(String subject, List<SimpleMembreInOutData> data) {
if (data == null)
return Uni.createFrom().nullItem();
final List<SimpleMembreInOutData> data2 = data.stream()
.filter(dataIn -> dataIn.getNom() != null && !dataIn.getNom()
.isBlank() && dataIn.getPrenom() != null && !dataIn.getPrenom().isBlank()).toList();
if (data2.isEmpty())
return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>();
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
clubModel.set(membreModel.getClub());
if (data2.stream().noneMatch(d -> d.getLicence() != null))
return Uni.createFrom().item(new ArrayList<MembreModel>());
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get()))
throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) {
MembreModel model = membres.stream()
.filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname()
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom())).findFirst()
.orElseGet(() -> {
MembreModel mm = new MembreModel();
mm.setClub(clubModel.get());
mm.setLicences(new ArrayList<>());
mm.setCountry("FR");
return mm;
});
if ((model.getId() != null && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom()
.toUpperCase()) > 3) || (model.getId() != null && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
}
model.setLname(dataIn.getNom().toUpperCase());
model.setFname(dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1));
if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank())
model.setEmail(dataIn.getEmail());
model.setGenre(Genre.fromString(dataIn.getGenre()));
if (dataIn.getBirthdate() != null) {
model.setBirth_date(dataIn.getBirthdate());
model.setCategorie(Utils.getCategoryFormBirthDate(model.getBirth_date(), new Date()));
}
uniResult = uniResult.call(() -> Panache.withTransaction(() -> repository.persist(model)
.chain(membreModel1 -> dataIn.isLicenceCurrent() ? licenceRepository.find(
"membre.id = ?1 AND saison = ?2", membreModel1.getId(), Utils.getSaison())
.firstResult()
.call(l -> {
if (l == null) {
l = new LicenceModel();
l.setMembre(membreModel1);
l.setValidate(false);
l.setSaison(Utils.getSaison());
}
l.setCertificate(dataIn.getCertif());
return licenceRepository.persist(l);
}) : licenceRepository.delete(
"membre = ?1 AND saison = ?2 AND validate = false", membreModel1,
Utils.getSaison()))));
}
return uniResult;
}))
.map(__ -> "OK");
}
public Uni<MembreModel> getById(long id) {
return repository.findById(id);
}

View File

@ -71,6 +71,21 @@ public class MembreClubEndpoints {
return membreService.getAllExport(securityCtx.getSubject());
}
@PUT
@Path("club/import")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Importer les membres du club", description = "Importer tout ou en partie les membres du club")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les membres du club ont été importés avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> importMembre(List<SimpleMembreInOutData> dataIn) {
System.out.println("importMembre");
return membreService.allImporte(securityCtx.getSubject(), dataIn);
}
@PUT
@Path("club/{id}")
@Produces(MediaType.TEXT_PLAIN)

View File

@ -14,6 +14,19 @@ public enum Genre {
this.str = name;
}
public static Genre fromString(String genre) {
if (genre == null) {
return NA;
}
if (genre.equalsIgnoreCase("Homme") || genre.equalsIgnoreCase("H")) {
return H;
} else if (genre.equalsIgnoreCase("Femme") || genre.equalsIgnoreCase("F")) {
return F;
} else {
return NA;
}
}
@Override
public String toString() {
return str;

View File

@ -193,8 +193,6 @@ function FileOutput() {
function FileInput() {
const [data, setData] = useState(null);
const re = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
function excelDateToJSDate(serial) {
@ -251,19 +249,30 @@ function FileInput() {
if (line["Nom médecin certificat"] === undefined || line["Nom médecin certificat"] === "" ||
line["Date certificat"] === undefined || line["Date certificat"] === "") {
cetifNotFill++;
}
try {
// noinspection JSNonASCIINames
tmp.certif = line["Nom médecin certificat"] + "¤" + excelDateToJSDate(line["Date certificat"]).toISOString()
} catch (e) {
toast.error("Format de la date de certificat invalide à la ligne " + (i + 2))
error++;
} else {
try {
const date = excelDateToJSDate(line["Date certificat"]);
if (Number.isNaN(date.getFullYear())) {
toast.error("Format de la date de certificat invalide à la ligne " + (i + 2))
error++;
} else {
// noinspection JSNonASCIINames
tmp.certif = line["Nom médecin certificat"] + "¤" + date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2);
}
} catch (e) {
toast.error("Format de la date de certificat invalide à la ligne " + (i + 2))
error++;
}
}
if (tmp.birthdate === undefined || tmp.birthdate === "") {
toast.error("Date de naissance vide à la ligne " + (i + 2))
error++;
}
}
if (tmp.birthdate !== undefined && tmp.birthdate !== "") {
console.log(tmp.birthdate);
try {
tmp.birthdate = excelDateToJSDate(tmp.birthdate).toISOString();
} catch (e) {
@ -271,6 +280,7 @@ function FileInput() {
error++;
}
}
dataOut.push(tmp)
}
@ -278,7 +288,22 @@ function FileInput() {
toast.error(`${error} erreur(s) dans le fichier, opération annulée`)
} else {
console.log(dataOut);
setData(sheetData);
toast.promise(
apiAxios.put(`/member/club/import`, dataOut),
{
pending: "Envoie des changement en cours",
success: "Changement envoyé avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de l'envoie des changements")
}
}
}
).then(_ => {
if (cetifNotFill > 0)
toast.warn(`${cetifNotFill} certificat(s) médical(aux) non rempli(s)`)
})
}
};