/** * Utilitaire de validation du NIR (numéro de sécurité sociale français). * - Format 15 caractères (chiffres ou 2A/2B pour la Corse). * - Clé de contrôle : 97 - (NIR13 mod 97). Pour 2A/2B, conversion temporaire (INSEE : 2A→19, 2B→20). * - En cas d'incohérence avec les données (sexe, date, lieu) : warning uniquement, pas de rejet. */ const NIR_CORSE_2A = '19'; const NIR_CORSE_2B = '20'; /** Regex 15 caractères : sexe (1-3) + 4 chiffres + (2A|2B|2 chiffres) + 6 chiffres + 2 chiffres clé */ const NIR_FORMAT = /^[1-3]\d{4}(?:2A|2B|\d{2})\d{6}\d{2}$/i; /** * Convertit le NIR en chaîne de 13 chiffres pour le calcul de la clé (2A→19, 2B→20). */ export function nirTo13Digits(nir: string): string { const n = nir.toUpperCase().replace(/\s/g, ''); if (n.length !== 15) return ''; const dept = n.slice(5, 7); let deptNum: string; if (dept === '2A') deptNum = NIR_CORSE_2A; else if (dept === '2B') deptNum = NIR_CORSE_2B; else deptNum = dept; return n.slice(0, 5) + deptNum + n.slice(7, 13); } /** * Vérifie que le format NIR est valide (15 caractères, 2A/2B acceptés). */ export function isNirFormatValid(nir: string): boolean { if (!nir || typeof nir !== 'string') return false; const n = nir.replace(/\s/g, '').toUpperCase(); return NIR_FORMAT.test(n); } /** * Calcule la clé de contrôle attendue (97 - (NIR13 mod 97)). * Retourne un nombre entre 1 et 97. */ export function computeNirKey(nir13: string): number { const num = parseInt(nir13, 10); if (Number.isNaN(num) || nir13.length !== 13) return -1; return 97 - (num % 97); } /** * Vérifie la clé de contrôle du NIR (15 caractères). * Retourne true si le NIR est valide (format + clé). */ export function isNirKeyValid(nir: string): boolean { const n = nir.replace(/\s/g, '').toUpperCase(); if (n.length !== 15) return false; const nir13 = nirTo13Digits(n); if (nir13.length !== 13) return false; const expectedKey = computeNirKey(nir13); const actualKey = parseInt(n.slice(13, 15), 10); return expectedKey === actualKey; } export interface NirValidationResult { valid: boolean; error?: string; warning?: string; } /** * Valide le NIR (format + clé). En cas d'incohérence avec date de naissance ou sexe, ajoute un warning sans invalider. */ export function validateNir( nir: string, options?: { dateNaissance?: string; genre?: 'H' | 'F' }, ): NirValidationResult { const n = (nir || '').replace(/\s/g, '').toUpperCase(); if (n.length === 0) return { valid: false, error: 'Le NIR est requis' }; if (!isNirFormatValid(n)) { return { valid: false, error: 'Le NIR doit contenir 15 caractères (chiffres, ou 2A/2B pour la Corse)' }; } if (!isNirKeyValid(n)) { return { valid: false, error: 'Clé de contrôle du NIR invalide' }; } let warning: string | undefined; if (options?.genre) { const sexNir = n[0]; const expectedSex = options.genre === 'F' ? '2' : '1'; if (sexNir !== expectedSex) { warning = 'Le NIR ne correspond pas au genre indiqué (position 1 du NIR).'; } } if (options?.dateNaissance) { try { const d = new Date(options.dateNaissance); if (!Number.isNaN(d.getTime())) { const year2 = d.getFullYear() % 100; const month = d.getMonth() + 1; const nirYear = parseInt(n.slice(1, 3), 10); const nirMonth = parseInt(n.slice(3, 5), 10); if (nirYear !== year2 || nirMonth !== month) { warning = warning ? `${warning} Le NIR ne correspond pas à la date de naissance (positions 2-5).` : 'Le NIR ne correspond pas à la date de naissance indiquée (positions 2-5).'; } } } catch { // ignore } } return { valid: true, warning }; }