first commit

This commit is contained in:
mrtoine 2025-09-20 13:18:04 +02:00
commit e6c52820cd
227 changed files with 16156 additions and 0 deletions

181
modules/crm/search.py Normal file
View 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