diff --git a/api/em-api.php b/api/em-api.php index 31b542175..79dddd883 100644 --- a/api/em-api.php +++ b/api/em-api.php @@ -435,6 +435,169 @@ case "universal-stats": break; +case "candidates": + // GET /api/em/candidates?tenant=weval&client=OCP_SAP_SUPPLY&status=validated&min_score=0.5 + $tenant = $_GET["tenant"] ?? "weval"; + $client = $_GET["client"] ?? null; + $status = $_GET["status"] ?? null; + $min_score = isset($_GET["min_score"]) ? floatval($_GET["min_score"]) : null; + $id = $parts[1] ?? null; + + if ($_SERVER["REQUEST_METHOD"] === "POST" && !$id) { + // Create candidate + $body = json_decode(file_get_contents("php://input"), true) ?: $_POST; + if (empty($body["full_name"])) { http_response_code(400); echo json_encode(["error"=>"full_name required"]); break; } + $stmt = $pdo->prepare("INSERT INTO weval.candidates (tenant_id, client_code, full_name, phone, email, age, nationality, location, diploma, school, experience_years, status, notes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"); + $stmt->execute([ + $tenant, $body["client_code"] ?? null, $body["full_name"], + $body["phone"] ?? null, $body["email"] ?? null, + $body["age"] ?? null, $body["nationality"] ?? null, $body["location"] ?? null, + $body["diploma"] ?? null, $body["school"] ?? null, + $body["experience_years"] ?? null, + $body["status"] ?? "sourced", + $body["notes"] ?? null + ]); + $new_id = $stmt->fetchColumn(); + audit($pdo, "candidate_add", (string)$new_id, $body); + echo json_encode(["ok"=>true, "id"=>$new_id, "full_name"=>$body["full_name"]]); + break; + } + + if ($id && ($parts[2] ?? "") === "score" && $_SERVER["REQUEST_METHOD"] === "POST") { + // Score a candidate + $body = json_decode(file_get_contents("php://input"), true) ?: $_POST; + $hard = floatval($body["hard_score"] ?? 0); + $soft = floatval($body["soft_score"] ?? 0); + $total = ($hard + $soft) / 2; + $pdo->prepare("UPDATE weval.candidates SET hard_score=?, soft_score=?, total_score=?, updated_at=NOW() WHERE id=? AND tenant_id=?")->execute([$hard,$soft,$total,$id,$tenant]); + $pdo->prepare("INSERT INTO weval.candidate_scoring (candidate_id, tenant_id, scorer, hard_score, soft_score, total_score, breakdown, comment) VALUES (?,?,?,?,?,?,?,?)")->execute([$id,$tenant,$body["scorer"]??"system",$hard,$soft,$total,json_encode($body["breakdown"]??[]),$body["comment"]??null]); + audit($pdo, "candidate_score", (string)$id, ["hard"=>$hard,"soft"=>$soft,"total"=>$total]); + echo json_encode(["ok"=>true, "id"=>$id, "hard_score"=>$hard, "soft_score"=>$soft, "total_score"=>$total]); + break; + } + + if ($id && ($parts[2] ?? "") === "validate" && $_SERVER["REQUEST_METHOD"] === "POST") { + // Candidate -> Consultant + $body = json_decode(file_get_contents("php://input"), true) ?: $_POST; + $cand = $pdo->prepare("SELECT * FROM weval.candidates WHERE id=? AND tenant_id=?"); + $cand->execute([$id, $tenant]); + $c = $cand->fetch(PDO::FETCH_ASSOC); + if (!$c) { http_response_code(404); echo json_encode(["error"=>"candidate_not_found"]); break; } + // Generate code + $code = "CST_" . date("Y") . "_" . str_pad($id, 3, "0", STR_PAD_LEFT); + $seniority = ($c["experience_years"] ?? 0) >= 10 ? "senior" : (($c["experience_years"] ?? 0) >= 5 ? "confirmed" : "junior"); + $stmt = $pdo->prepare("INSERT INTO weval.consultants (tenant_id, candidate_id, consultant_code, full_name, email, phone, role, seniority, tjm_default, commission_rate, entity, status, hire_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"); + $stmt->execute([$tenant, $id, $code, $c["full_name"], $c["email"], $c["phone"], + $body["role"] ?? "Consultant", $seniority, + $body["tjm"] ?? 2470, $body["commission"] ?? 0.05, + $body["entity"] ?? "BA Solution", "active", date("Y-m-d")]); + $cst_id = $stmt->fetchColumn(); + $pdo->prepare("UPDATE weval.candidates SET status='placed', updated_at=NOW() WHERE id=?")->execute([$id]); + audit($pdo, "candidate_validate", (string)$id, ["consultant_id"=>$cst_id,"code"=>$code]); + echo json_encode(["ok"=>true, "candidate_id"=>$id, "consultant_id"=>$cst_id, "consultant_code"=>$code]); + break; + } + + if ($id) { + // GET single + $stmt = $pdo->prepare("SELECT * FROM weval.candidates WHERE id=? AND tenant_id=?"); + $stmt->execute([$id, $tenant]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$row) { http_response_code(404); echo json_encode(["error"=>"not_found"]); break; } + // Attach skills history + $sk = $pdo->prepare("SELECT * FROM weval.candidate_skills WHERE candidate_id=?"); + $sk->execute([$id]); + $row["skills"] = $sk->fetchAll(PDO::FETCH_ASSOC); + $hist = $pdo->prepare("SELECT * FROM weval.candidate_scoring WHERE candidate_id=? ORDER BY scored_at DESC LIMIT 10"); + $hist->execute([$id]); + $row["scoring_history"] = $hist->fetchAll(PDO::FETCH_ASSOC); + echo json_encode($row); + break; + } + + // List + $sql = "SELECT id, full_name, status, short_list, experience_years, hard_score, soft_score, total_score, target_roles, client_code, new_flag, internal FROM weval.candidates WHERE tenant_id=?"; + $params = [$tenant]; + if ($client) { $sql .= " AND client_code=?"; $params[] = $client; } + if ($status) { $sql .= " AND status=?"; $params[] = $status; } + if ($min_score !== null) { $sql .= " AND total_score >= ?"; $params[] = $min_score; } + $sql .= " ORDER BY total_score DESC, experience_years DESC NULLS LAST"; + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as &$r) { $r["target_roles"] = json_decode($r["target_roles"], true); } + echo json_encode(["tenant"=>$tenant, "count"=>count($rows), "candidates"=>$rows]); + break; + +case "missions": + $tenant = $_GET["tenant"] ?? "weval"; + $id = $parts[1] ?? null; + + if ($_SERVER["REQUEST_METHOD"] === "POST" && !$id) { + $body = json_decode(file_get_contents("php://input"), true) ?: $_POST; + if (empty($body["consultant_id"])) { http_response_code(400); echo json_encode(["error"=>"consultant_id required"]); break; } + $mcode = $body["mission_code"] ?? ("MIS_" . date("Y") . "_" . substr(md5(uniqid()), 0, 6)); + $stmt = $pdo->prepare("INSERT INTO weval.missions (tenant_id, mission_code, consultant_id, client_code, client_name, deal_id, role, tjm, commission_rate, start_date, end_date, status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"); + $stmt->execute([$tenant, $mcode, $body["consultant_id"], + $body["client_code"] ?? null, $body["client_name"] ?? null, + $body["deal_id"] ?? null, $body["role"] ?? "Consultant", + $body["tjm"] ?? 2470, $body["commission_rate"] ?? 0.05, + $body["start_date"] ?? date("Y-m-d"), $body["end_date"] ?? null, + $body["status"] ?? "planned"]); + $new_id = $stmt->fetchColumn(); + audit($pdo, "mission_create", (string)$new_id, $body); + echo json_encode(["ok"=>true, "id"=>$new_id, "mission_code"=>$mcode]); + break; + } + + if ($id && ($parts[2] ?? "") === "billing" && $_SERVER["REQUEST_METHOD"] === "POST") { + $body = json_decode(file_get_contents("php://input"), true) ?: $_POST; + $period = $body["period_month"] ?? date("Y-m-01"); + $days = floatval($body["days_worked"] ?? 0); + $tjm = floatval($body["tjm_applied"] ?? 2470); + $c_chafik = floatval($body["commission_chafik"] ?? 0.05); + $c_youssef = floatval($body["commission_youssef"] ?? 0.25); + $gross = $days * $tjm; + $com_c = $gross * $c_chafik; + $com_y = $gross * $c_youssef; + $cash = $gross - $com_c - $com_y; + $stmt = $pdo->prepare("INSERT INTO weval.mission_billing (tenant_id, mission_id, period_month, days_worked, tjm_applied, commission_chafik, commission_youssef, cash_consultant) VALUES (?,?,?,?,?,?,?,?) ON CONFLICT (mission_id, period_month) DO UPDATE SET days_worked=EXCLUDED.days_worked, tjm_applied=EXCLUDED.tjm_applied, cash_consultant=EXCLUDED.cash_consultant RETURNING id"); + $stmt->execute([$tenant, $id, $period, $days, $tjm, $c_chafik, $c_youssef, $cash]); + $bid = $stmt->fetchColumn(); + audit($pdo, "mission_bill", (string)$id, ["period"=>$period,"days"=>$days,"gross"=>$gross,"cash"=>$cash]); + echo json_encode(["ok"=>true, "billing_id"=>$bid, "mission_id"=>$id, "period"=>$period, "days"=>$days, "gross"=>$gross, "commission_chafik"=>$com_c, "commission_youssef"=>$com_y, "cash_consultant"=>$cash]); + break; + } + + if ($id) { + $stmt = $pdo->prepare("SELECT m.*, c.full_name as consultant_name, c.role as consultant_role FROM weval.missions m LEFT JOIN weval.consultants c ON c.id = m.consultant_id WHERE m.id=? AND m.tenant_id=?"); + $stmt->execute([$id, $tenant]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$row) { http_response_code(404); echo json_encode(["error"=>"not_found"]); break; } + $b = $pdo->prepare("SELECT id, period_month, days_worked, tjm_applied, gross_amount, commission_chafik, commission_youssef, cash_consultant, invoiced, paid FROM weval.mission_billing WHERE mission_id=? ORDER BY period_month"); + $b->execute([$id]); + $row["billing"] = $b->fetchAll(PDO::FETCH_ASSOC); + $row["total_gross"] = array_sum(array_column($row["billing"], "gross_amount")); + $row["total_days"] = array_sum(array_column($row["billing"], "days_worked")); + $row["total_cash"] = array_sum(array_column($row["billing"], "cash_consultant")); + echo json_encode($row); + break; + } + + // List missions + $stmt = $pdo->prepare("SELECT m.id, m.mission_code, m.client_code, m.client_name, m.role, m.tjm, m.start_date, m.end_date, m.status, c.full_name as consultant_name FROM weval.missions m LEFT JOIN weval.consultants c ON c.id = m.consultant_id WHERE m.tenant_id=? ORDER BY m.start_date DESC"); + $stmt->execute([$tenant]); + echo json_encode(["tenant"=>$tenant, "missions"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]); + break; + +case "consultants": + $tenant = $_GET["tenant"] ?? "weval"; + $stmt = $pdo->prepare("SELECT id, consultant_code, full_name, role, seniority, tjm_default, commission_rate, entity, status, hire_date FROM weval.consultants WHERE tenant_id=? ORDER BY hire_date DESC NULLS LAST, id"); + $stmt->execute([$tenant]); + echo json_encode(["tenant"=>$tenant, "consultants"=>$stmt->fetchAll(PDO::FETCH_ASSOC)]); + break; + + default: echo json_encode([ "service" => "WEVIA EM API", diff --git a/cartographie-screens.html b/cartographie-screens.html index 826b4fedf..721d64ba1 100644 --- a/cartographie-screens.html +++ b/cartographie-screens.html @@ -70,7 +70,7 @@ select{padding:10px;background:#0a0e27;color:#fff;border:1px solid #3d4476;borde