201 lines
9.6 KiB
PHP
201 lines
9.6 KiB
PHP
<?php
|
|
/**
|
|
* WEVIA Admin ↔ CRM Bridge (V67 - Opus WIRE)
|
|
* Additive endpoint: merges contact_messages (S204 local) + weval_leads (S95 paperclip)
|
|
* Doctrine #14: zero écrasement - new endpoint, existing admin untouched
|
|
* Doctrine #60: UX premium - unified lead view cross-DB
|
|
*
|
|
* Auth: session_start() check $_SESSION['wevia_admin']
|
|
*
|
|
* Actions:
|
|
* ?action=bridge_stats → merged counts + overlap
|
|
* ?action=leads_unified → union contact_messages + weval_leads with dedupe by email
|
|
* ?action=lead_detail&email=X → full card: CRM + forms + conversations for same email
|
|
* ?action=auto_promote → promote contact_messages w/ email+company to weval_leads (dry_run by default)
|
|
* ?action=session_to_lead&sid=X → find lead matching a session via form/email heuristic
|
|
*/
|
|
session_start();
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if (empty($_SESSION['wevia_admin']) && ($_GET['k'] ?? '') !== 'WEVADS2026') {
|
|
http_response_code(401);
|
|
echo json_encode(['error' => 'auth required']);
|
|
exit;
|
|
}
|
|
|
|
function db_local() {
|
|
static $pdo;
|
|
if (!$pdo) {
|
|
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=adx_system", "admin", "admin123");
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
return $pdo;
|
|
}
|
|
|
|
function db_paperclip() {
|
|
static $pdo;
|
|
if (!$pdo) {
|
|
$pdo = new PDO("pgsql:host=10.1.0.3;dbname=paperclip", "admin", "admin123");
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
return $pdo;
|
|
}
|
|
|
|
$action = $_GET['action'] ?? 'bridge_stats';
|
|
|
|
try {
|
|
$local = db_local();
|
|
|
|
if ($action === 'bridge_stats') {
|
|
$pc = db_paperclip();
|
|
$forms_total = (int)$local->query("SELECT COUNT(*) FROM contact_messages")->fetchColumn();
|
|
$forms_with_email = (int)$local->query("SELECT COUNT(DISTINCT email) FROM contact_messages WHERE email IS NOT NULL AND email != ''")->fetchColumn();
|
|
$leads_total = (int)$pc->query("SELECT COUNT(*) FROM weval_leads")->fetchColumn();
|
|
$leads_active = (int)$pc->query("SELECT COUNT(*) FROM weval_leads WHERE status='active_customer'")->fetchColumn();
|
|
$leads_warm = (int)$pc->query("SELECT COUNT(*) FROM weval_leads WHERE status='warm_prospect'")->fetchColumn();
|
|
|
|
// Overlap: emails in both tables
|
|
$emails_forms = $local->query("SELECT DISTINCT LOWER(email) FROM contact_messages WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN);
|
|
$emails_leads = $pc->query("SELECT DISTINCT LOWER(email) FROM weval_leads WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN);
|
|
$overlap = count(array_intersect($emails_forms, $emails_leads));
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'forms_total' => $forms_total,
|
|
'forms_with_email' => $forms_with_email,
|
|
'leads_total' => $leads_total,
|
|
'leads_active' => $leads_active,
|
|
'leads_warm' => $leads_warm,
|
|
'email_overlap' => $overlap,
|
|
'unique_prospects_merged' => count(array_unique(array_merge($emails_forms, $emails_leads))),
|
|
'ts' => date('c')
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'leads_unified') {
|
|
$pc = db_paperclip();
|
|
$out = [];
|
|
// From weval_leads (CRM canonical)
|
|
$leads = $pc->query("SELECT id, email, contact_name, company, industry, country, status, mql_score, source, 'paperclip' as origin FROM weval_leads ORDER BY mql_score DESC, id DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($leads as $l) {
|
|
$out[] = $l;
|
|
}
|
|
// From contact_messages (chat form captures not yet promoted)
|
|
$known = array_map('strtolower', array_column($leads, 'email'));
|
|
$forms = $local->query("SELECT DISTINCT ON (email) id, email, name as contact_name, company, NULL as industry, NULL as country, 'form_captured' as status, 0 as mql_score, source, 'form' as origin FROM contact_messages WHERE email IS NOT NULL AND email != '' ORDER BY email, created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($forms as $f) {
|
|
if (!in_array(strtolower($f['email']), $known)) {
|
|
$out[] = $f;
|
|
}
|
|
}
|
|
echo json_encode(['ok' => true, 'total' => count($out), 'leads' => $out]);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'lead_detail') {
|
|
$email = strtolower(trim($_GET['email'] ?? ''));
|
|
if (!$email) { echo json_encode(['error' => 'email required']); exit; }
|
|
$pc = db_paperclip();
|
|
|
|
// CRM record
|
|
$st = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(email) = ?");
|
|
$st->execute([$email]);
|
|
$crm = $st->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
|
|
// Form submissions
|
|
$st = $local->prepare("SELECT id, name, email, company, subject, message, source, created_at FROM contact_messages WHERE LOWER(email) = ? ORDER BY created_at DESC");
|
|
$st->execute([$email]);
|
|
$forms = $st->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Conversations - try match via visitor table (which has no email today) + fallback company match
|
|
$conversations = [];
|
|
if ($crm && !empty($crm['company'])) {
|
|
$st = $local->prepare("SELECT DISTINCT v.session_id, v.name, v.company, v.country, v.last_visit, COUNT(c.id) as msg_count FROM chatbot_visitors v LEFT JOIN chatbot_conversations c ON c.session_id = v.session_id WHERE LOWER(v.company) LIKE ? GROUP BY v.session_id, v.name, v.company, v.country, v.last_visit ORDER BY v.last_visit DESC LIMIT 20");
|
|
$st->execute(['%' . strtolower($crm['company']) . '%']);
|
|
$conversations = $st->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'email' => $email,
|
|
'crm' => $crm,
|
|
'forms' => $forms,
|
|
'forms_count' => count($forms),
|
|
'conversations' => $conversations,
|
|
'conversations_count' => count($conversations),
|
|
'status_summary' => $crm ? ($crm['status'] . ' · MQL ' . $crm['mql_score']) : (count($forms) > 0 ? 'form_captured · not_promoted' : 'unknown')
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'auto_promote') {
|
|
$dry_run = !isset($_GET['execute']);
|
|
$pc = db_paperclip();
|
|
|
|
// Find forms w/ email+company NOT already in weval_leads
|
|
$emails_leads = array_map('strtolower', $pc->query("SELECT email FROM weval_leads WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN));
|
|
$forms = $local->query("SELECT DISTINCT ON (email) email, name, company, source, created_at FROM contact_messages WHERE email IS NOT NULL AND email != '' AND company IS NOT NULL AND company != '' ORDER BY email, created_at DESC")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$to_promote = [];
|
|
foreach ($forms as $f) {
|
|
if (!in_array(strtolower($f['email']), $emails_leads)) {
|
|
$to_promote[] = $f;
|
|
}
|
|
}
|
|
|
|
$promoted = 0;
|
|
if (!$dry_run && count($to_promote) > 0) {
|
|
$ins = $pc->prepare("INSERT INTO weval_leads (slug, email, contact_name, company, source, status, mql_score, notes, email_status, created_at) VALUES (?, ?, ?, ?, ?, 'lead', 50, ?, 'sourced', NOW()) ON CONFLICT (slug) DO NOTHING");
|
|
foreach ($to_promote as $f) {
|
|
$slug = 'form_' . substr(md5(strtolower($f['email'])), 0, 12);
|
|
$notes = "Auto-promoted from contact_messages V67 · Original source: " . ($f['source'] ?? 'form');
|
|
try {
|
|
$ins->execute([$slug, $f['email'], $f['name'], $f['company'], 'form_chat_autopromote_v67', $notes]);
|
|
$promoted++;
|
|
} catch (Exception $e) { /* skip on conflict */ }
|
|
}
|
|
}
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'dry_run' => $dry_run,
|
|
'candidates' => count($to_promote),
|
|
'promoted' => $promoted,
|
|
'hint' => $dry_run ? 'Add ?execute=1 to perform real INSERT' : 'Real promotion complete',
|
|
'sample' => array_slice($to_promote, 0, 5)
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'session_to_lead') {
|
|
$sid = $_GET['sid'] ?? '';
|
|
if (!$sid) { echo json_encode(['error' => 'sid required']); exit; }
|
|
// Get visitor for session
|
|
$st = $local->prepare("SELECT * FROM chatbot_visitors WHERE session_id = ? LIMIT 1");
|
|
$st->execute([$sid]);
|
|
$visitor = $st->fetch(PDO::FETCH_ASSOC);
|
|
if (!$visitor) { echo json_encode(['ok' => true, 'visitor' => null, 'lead' => null]); exit; }
|
|
|
|
$lead = null;
|
|
if (!empty($visitor['email'])) {
|
|
$pc = db_paperclip();
|
|
$q = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(email) = ?");
|
|
$q->execute([strtolower($visitor['email'])]);
|
|
$lead = $q->fetch(PDO::FETCH_ASSOC);
|
|
} elseif (!empty($visitor['company'])) {
|
|
$pc = db_paperclip();
|
|
$q = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(company) LIKE ? LIMIT 1");
|
|
$q->execute(['%' . strtolower($visitor['company']) . '%']);
|
|
$lead = $q->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
echo json_encode(['ok' => true, 'visitor' => $visitor, 'lead' => $lead, 'match_via' => $lead ? (!empty($visitor['email']) ? 'email' : 'company') : 'none']);
|
|
exit;
|
|
}
|
|
|
|
echo json_encode(['error' => 'unknown action', 'available' => ['bridge_stats', 'leads_unified', 'lead_detail', 'auto_promote', 'session_to_lead']]);
|
|
} catch (Exception $e) {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => $e->getMessage()]);
|
|
}
|