diff --git a/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java b/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java index f968679..b851829 100644 --- a/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java +++ b/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java @@ -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; + } }