diff --git a/app/routers/planning_import.py b/app/routers/planning_import.py index cc06b8d..5418904 100644 --- a/app/routers/planning_import.py +++ b/app/routers/planning_import.py @@ -1178,29 +1178,54 @@ def _fetch_pct_cc_emails(db, row_ids): if len(part) >= 4 and re.search(r"[A-Za-zÀ-ÿ]{3}", part): legacy_names.add(part) - # 4) Match par nom dans la table contacts pour les responsables/référents - # qui n'ont pas d'email côté servers. Match insensible casse + accents. + # 4) Match par parsing du nom complet → email SANEF (format prenom.nom@sanef.com) + # Le contacts.name en BDD est juste le NOM DE FAMILLE (ex 'DELCOUR'), + # et il y a souvent plusieurs DELCOUR — il faut matcher ET le nom ET le prénom. if legacy_names: - # On construit une condition ILIKE pour chaque nom + ses variantes simples - names_list = list(legacy_names)[:50] # limite de sécurité - params = {} - ors = [] - for i, n in enumerate(names_list): - params[f"n{i}"] = n - # Match exact (case insensible) ou nom contenu (utile pour 'DUFOUR Antoine' vs 'Antoine Dufour') - ors.append(f"LOWER(c.name) = LOWER(:n{i})") - ors.append(f"LOWER(c.name) LIKE LOWER('%' || :n{i} || '%')") - sql = ( - "SELECT DISTINCT c.name, c.email FROM contacts c " - "WHERE c.email IS NOT NULL AND c.email <> '' " - f"AND ({' OR '.join(ors)})" - ) - try: - matched = db.execute(text(sql), params).fetchall() - for c in matched: - _add(c.name, c.email) - except Exception as e: - print(f"[pct_cc] match par nom failed: {e}") + pairs = [] # liste de (prenom, nom) + for raw in legacy_names: + # Nettoie suffixes parasites courants : "- PCT", "- DBA", "(externe)", etc. + s = re.sub(r"\s*[\(\[].*?[\)\]]\s*", " ", raw) + s = re.sub(r"\s*-\s*[A-Z]{2,5}\s*$", "", s).strip() # "- PCT" en fin + if not s: + continue + tokens = re.split(r"\s+", s) + tokens = [t for t in tokens if t and t not in ("PCT", "DBA", "ext", "Ext")] + if len(tokens) < 2: + continue + # Identifie le nom de famille = token UPPERCASE de longueur >= 3 + nom = None + for t in tokens: + # Match nom de famille : uniquement majuscules ASCII, longueur >= 3 + # (gère 'DUFOUR', 'DELCOUR', 'GRAFFAGNINO' ; pas 'PCT' qui est trop court) + tt = t.replace("-", "").replace("'", "") + if len(tt) >= 3 and tt.isupper(): + nom = t + break + if nom: + # Prénom = 1er token qui n'est pas le nom (souvent le 1er token) + prenom = next((t for t in tokens if t != nom), None) + else: + # Pas de UPPERCASE détecté : on suppose et prend dernier token + prenom = tokens[0] + nom = tokens[-1] + if prenom and nom and prenom != nom: + pairs.append((prenom, nom)) + + # Pour chaque (prenom, nom), match strict sur email SANEF + for prenom, nom in pairs[:50]: # limite de sécurité + # email format SANEF : prenom.nom@... (insensible casse, accents non gérés en BDD) + email_pattern = f"{prenom.lower()}.{nom.lower()}@%" + try: + matched = db.execute(text(""" + SELECT name, email FROM contacts + WHERE email IS NOT NULL AND email <> '' + AND LOWER(email) LIKE :pat + """), {"pat": email_pattern}).fetchall() + for c in matched: + _add(f"{prenom} {nom}", c.email) + except Exception as e: + print(f"[pct_cc] match {prenom}.{nom} failed: {e}") out.sort(key=lambda c: c["name"].lower()) return out