false, 'message' => 'Invalid request method']); exit; } try { $input = file_get_contents('php://input'); $data = json_decode($input, true); if (!$data) { throw new Exception('Invalid JSON input'); } $selection_id = (int)($data['selection_id'] ?? 0); if (!$selection_id) { throw new Exception('Invalid selection ID'); } $pdo = getClientDBConnection(); // 1. Get selection details + project info $stmt = $pdo->prepare(" SELECT ps.*, p.project_id AS project_code, p.project_name, p.id AS proj_id, p.status AS project_status FROM project_selections ps JOIN projects p ON ps.project_id = p.id WHERE ps.id = ? AND ps.client_id = ? "); $stmt->execute([$selection_id, $_SESSION['client_id']]); $selection = $stmt->fetch(PDO::FETCH_ASSOC); if (!$selection) { throw new Exception('Selection not found or access denied'); } $project_code = $selection['project_code']; // 2. Count members in this selection $stmt = $pdo->prepare("SELECT COUNT(*) FROM selection_members WHERE selection_id = ?"); $stmt->execute([$selection_id]); $member_count = (int)$stmt->fetchColumn(); if ($member_count === 0) { echo json_encode([ 'success' => false, 'message' => 'No members in this selection. Create the selection with members first.' ]); exit; } // 3. Count URLs already assigned to THIS selection $stmt = $pdo->prepare(" SELECT COUNT(*) FROM survey_urls WHERE project_id = ? AND assigned_to_selection_id = ? "); $stmt->execute([$project_code, $selection_id]); $already_assigned = (int)$stmt->fetchColumn(); // 4. Calculate how many more URLs we need $needed = $member_count - $already_assigned; if ($needed <= 0) { echo json_encode([ 'success' => true, 'message' => 'All members already have URLs assigned.', 'assigned_count' => 0, 'total_assigned' => $already_assigned, 'member_count' => $member_count, 'fully_assigned' => true, 'shortfall' => 0 ]); exit; } // 5. Get available URLs — use intval inline for LIMIT (PDO param binding fails for LIMIT on many MySQL configs) $needed_int = intval($needed); $stmt = $pdo->prepare(" SELECT id FROM survey_urls WHERE project_id = ? AND status = 'available' AND assigned_to_selection_id IS NULL ORDER BY batch_number ASC, id ASC LIMIT {$needed_int} "); $stmt->execute([$project_code]); $available_url_ids = $stmt->fetchAll(PDO::FETCH_COLUMN); $available_count = count($available_url_ids); $shortfall = $needed - $available_count; // 6. Assign the URLs within a transaction if ($available_count > 0) { $pdo->beginTransaction(); try { $update_stmt = $pdo->prepare(" UPDATE survey_urls SET status = 'assigned', assigned_to_selection_id = ? WHERE id = ? AND status = 'available' AND assigned_to_selection_id IS NULL "); $assigned_count = 0; foreach ($available_url_ids as $url_id) { $update_stmt->execute([$selection_id, $url_id]); $assigned_count += $update_stmt->rowCount(); } $total_assigned = $already_assigned + $assigned_count; $fully_assigned = ($total_assigned >= $member_count); // Auto-update selection status to 'targeted' if fully assigned if ($fully_assigned && in_array($selection['status'], ['draft'])) { $stmt = $pdo->prepare("UPDATE project_selections SET status = 'targeted' WHERE id = ?"); $stmt->execute([$selection_id]); } // Check if ALL selections for this project are fully assigned → project 'Targeted' if ($fully_assigned) { $stmt = $pdo->prepare(" SELECT ps.id, COUNT(DISTINCT sm.user_id) AS member_count, COUNT(DISTINCT su.id) AS url_count FROM project_selections ps LEFT JOIN selection_members sm ON ps.id = sm.selection_id LEFT JOIN survey_urls su ON ps.id = su.assigned_to_selection_id WHERE ps.project_id = ? AND ps.client_id = ? GROUP BY ps.id "); $stmt->execute([$selection['project_id'], $_SESSION['client_id']]); $all_selections = $stmt->fetchAll(PDO::FETCH_ASSOC); $all_targeted = true; foreach ($all_selections as $s) { if ($s['member_count'] > 0 && $s['url_count'] < $s['member_count']) { $all_targeted = false; break; } } if ($all_targeted && $selection['project_status'] === 'Created') { $stmt = $pdo->prepare("UPDATE projects SET status = 'Targeted' WHERE id = ?"); $stmt->execute([$selection['proj_id']]); } } // ✅ FIX: selection_activity_log uses: description (not details), performed_by (not client_id), NO ip_address $details = json_encode(['assigned' => $assigned_count, 'total' => $total_assigned, 'members' => $member_count]); $stmt = $pdo->prepare(" INSERT INTO selection_activity_log (selection_id, action, description, performed_by, created_at) VALUES (?, 'urls_assigned', ?, ?, NOW()) "); $stmt->execute([$selection_id, $details, $_SESSION['client_id']]); $pdo->commit(); // Build response message $remaining = max(0, $member_count - $total_assigned); $msg = "Assigned $assigned_count URL(s)"; if ($remaining > 0) { $msg .= ". Still need $remaining more URL(s)."; } else { $msg .= ". All members have URLs assigned!"; if ($fully_assigned) { $msg .= " Selection status updated to 'targeted'."; } } echo json_encode([ 'success' => true, 'assigned_count' => $assigned_count, 'total_assigned' => $total_assigned, 'member_count' => $member_count, 'fully_assigned' => $fully_assigned, 'shortfall' => max(0, $shortfall), 'message' => $msg ]); } catch (Exception $e) { $pdo->rollBack(); throw $e; } } else { echo json_encode([ 'success' => false, 'message' => sprintf( 'No available survey URLs found for this project. %d URLs are required for %d members. Please upload survey URL batches first.', $needed, $member_count ), 'assigned_count' => 0, 'total_assigned' => $already_assigned, 'member_count' => $member_count, 'fully_assigned' => false, 'shortfall' => $needed ]); } } catch (Exception $e) { if (isset($pdo) && $pdo->inTransaction()) { $pdo->rollBack(); } error_log("Assign selection URLs error: " . $e->getMessage()); echo json_encode([ 'success' => false, 'message' => 'Error: ' . $e->getMessage() ]); }