import json import os from datetime import datetime from typing import List, Dict, Any, Optional def _project_root() -> str: # modules/crm/search.py -> remonter à la racine projet return os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def _prospects_dir() -> str: return os.path.join(_project_root(), "Data", "prospects") def _read_json(path: str) -> Optional[Dict[str, Any]]: try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception: return None def _norm_date(date_str: Optional[str]) -> Optional[str]: if not date_str: return None # Tente de normaliser vers YYYY-MM-DD s = str(date_str) # Si déjà au format ISO ou contient "T" if len(s) >= 10: return s[:10] return None def _extract_fields(data: Dict[str, Any], fallback_id: str) -> Dict[str, Any]: # Essaye plusieurs variantes de clés name = data.get("name") or data.get("nom") or data.get("full_name") or data.get("contact_name") or "" email = data.get("email") or data.get("mail") or "" company = data.get("company") or data.get("societe") or data.get("entreprise") or data.get("organization") or "" status = data.get("status") or data.get("statut") or "" city = data.get("city") or data.get("ville") or "" tags = data.get("tags") or [] if isinstance(tags, str): # transformer "a, b, c" -> ["a","b","c"] tags = [t.strip() for t in tags.split(",") if t.strip()] created_at = data.get("created_at") or data.get("created") or data.get("date_created") or "" created_at = _norm_date(created_at) or "" pid = data.get("id") or fallback_id # Liaisons potentielles client_id = data.get("client_id") or data.get("clientId") or data.get("client") project_ids = data.get("project_ids") or data.get("projects") or data.get("projectId") or [] if isinstance(project_ids, (str, int)): project_ids = [str(project_ids)] elif isinstance(project_ids, list): project_ids = [str(x) for x in project_ids] return { "id": pid, "entity_type": "prospect", "name": name, "email": email, "company": company, "status": status, "city": city, "tags": tags, "created_at": created_at, "client_id": client_id, "project_ids": project_ids, } def load_all_prospects() -> List[Dict[str, Any]]: base_dir = _prospects_dir() os.makedirs(base_dir, exist_ok=True) results: List[Dict[str, Any]] = [] for fn in os.listdir(base_dir): if not fn.endswith(".json"): continue path = os.path.join(base_dir, fn) data = _read_json(path) if not data: continue pid = os.path.splitext(fn)[0] results.append(_extract_fields(data, pid)) return results def _contains(hay: str, needle: str) -> bool: return needle in hay def _safe_lower(s: Optional[str]) -> str: return (s or "").lower() def filter_prospects( *, q: Optional[str] = None, name: Optional[str] = None, email: Optional[str] = None, company: Optional[str] = None, status: Optional[str] = None, city: Optional[str] = None, tags: Optional[List[str]] = None, date_from: Optional[str] = None, # YYYY-MM-DD date_to: Optional[str] = None, # YYYY-MM-DD ) -> List[Dict[str, Any]]: items = load_all_prospects() q = _safe_lower(q) name = _safe_lower(name) email = _safe_lower(email) company = _safe_lower(company) status = _safe_lower(status) city = _safe_lower(city) tags = [t.strip().lower() for t in (tags or []) if t.strip()] df = None dt = None try: if date_from: df = datetime.strptime(date_from, "%Y-%m-%d").date() except Exception: df = None try: if date_to: dt = datetime.strptime(date_to, "%Y-%m-%d").date() except Exception: dt = None out: List[Dict[str, Any]] = [] for it in items: iname = _safe_lower(it.get("name")) iemail = _safe_lower(it.get("email")) icompany = _safe_lower(it.get("company")) istatus = _safe_lower(it.get("status")) icity = _safe_lower(it.get("city")) itags = [str(t).lower() for t in (it.get("tags") or [])] icreated = it.get("created_at") or "" icreated_d = None try: if icreated: icreated_d = datetime.strptime(icreated[:10], "%Y-%m-%d").date() except Exception: icreated_d = None # Filtre global q if q and not ( _contains(iname, q) or _contains(iemail, q) or _contains(icompany, q) or _contains(icity, q) ): continue # Filtres spécifiques if name and not _contains(iname, name): continue if email and not _contains(iemail, email): continue if company and not _contains(icompany, company): continue if status and istatus != status: continue if city and not _contains(icity, city): continue if tags: # exiger que tous les tags demandés soient présents itags_set = set(itags) if not all(tag in itags_set for tag in tags): continue if df and (not icreated_d or icreated_d < df): continue if dt and (not icreated_d or icreated_d > dt): continue out.append(it) # On peut trier par date de création desc, puis nom out.sort(key=lambda d: (d.get("created_at") or "", d.get("name") or ""), reverse=True) return out