feat(types-ref): auto-génération du code technique depuis le libellé

TypeReferenceService.creer() :
- Si code non fourni ou vide : auto-généré depuis le libellé via genererCodeDepuisLibelle()
- Si code fourni : nettoyé via normaliserCode() (strip accents, UPPER_SNAKE_CASE)
- Dédoublonnage automatique : si le code existe, suffixe _2, _3, etc.

Méthodes ajoutées :
- genererCodeDepuisLibelle(String) : libellé → UPPER_SNAKE_CASE sans accents
  Ex: 'Mutuelle d''Épargne' → 'MUTUELLE_D_EPARGNE'
- normaliserCode(String) : Normalizer.NFD + strip diacritics + [^A-Za-z0-9] → _
- assurerUniciteCode(String, String, UUID) : suffixe incrémental si doublon

Les types système (est_systeme=true, seeded en V18) gardent leurs codes figés.
Seuls les types créés par l'utilisateur bénéficient de l'auto-génération.
This commit is contained in:
dahoud
2026-04-16 10:20:19 +00:00
parent 15479c0432
commit 48604bbbc6

View File

@@ -10,6 +10,7 @@ import dev.lions.unionflow.server.repository.TypeReferenceRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.text.Normalizer;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -118,14 +119,23 @@ public class TypeReferenceService {
@Transactional
public TypeReferenceResponse creer(
CreateTypeReferenceRequest request) {
// Auto-génération du code depuis le libellé si non fourni
// ou nettoyage si fourni (UPPER_SNAKE_CASE, sans accents ni caractères spéciaux)
String code = (request.code() != null && !request.code().isBlank())
? normaliserCode(request.code())
: genererCodeDepuisLibelle(request.libelle());
// Dédoublonnage : si le code existe déjà, suffixer _2, _3, etc.
code = assurerUniciteCode(code, request.domaine(), request.organisationId());
validerUnicite(
request.domaine(),
request.code(),
code,
request.organisationId());
TypeReference entity = TypeReference.builder()
.domaine(request.domaine())
.code(request.code())
.code(code)
.libelle(request.libelle())
.description(request.description())
.icone(request.icone())
@@ -364,4 +374,59 @@ public class TypeReferenceService {
+ domaine + "'");
}
}
// ── Auto-génération de codes techniques ─────────────────────────
/**
* Génère un code UPPER_SNAKE_CASE depuis un libellé.
* Ex: "Mon Association Locale" → "MON_ASSOCIATION_LOCALE"
* "Église de Dakar" → "EGLISE_DE_DAKAR"
* "Mutuelle d'Épargne" → "MUTUELLE_D_EPARGNE"
*/
static String genererCodeDepuisLibelle(String libelle) {
if (libelle == null || libelle.isBlank()) {
return "TYPE_" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
return normaliserCode(libelle);
}
/**
* Normalise une chaîne en code technique UPPER_SNAKE_CASE :
* strip accents, remplace tout non-alphanumérique par _, collapse _, trim _.
*/
static String normaliserCode(String input) {
// 1. Décomposer les accents puis les retirer
String s = Normalizer.normalize(input, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
// 2. Remplacer tout non-alphanumérique par underscore
s = s.replaceAll("[^A-Za-z0-9]+", "_");
// 3. Majuscules
s = s.toUpperCase();
// 4. Collapse underscores multiples et trim
s = s.replaceAll("_+", "_").replaceAll("^_|_$", "");
// 5. Safety : si vide après nettoyage
if (s.isEmpty()) {
s = "TYPE_" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
return s;
}
/**
* S'assure que le code est unique dans le domaine. Si le code existe déjà,
* suffixe avec _2, _3, etc. jusqu'à trouver un code libre.
*/
private String assurerUniciteCode(String baseCode, String domaine, UUID organisationId) {
String candidate = baseCode;
int suffix = 2;
while (repository.existsByDomaineAndCode(domaine, candidate, organisationId)) {
candidate = baseCode + "_" + suffix;
suffix++;
if (suffix > 100) {
// Protection anti-boucle infinie
candidate = baseCode + "_" + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
break;
}
}
return candidate;
}
}