first commit
This commit is contained in:
commit
e6c52820cd
227 changed files with 16156 additions and 0 deletions
181
modules/crm/search.py
Normal file
181
modules/crm/search.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue