validate([ 'lead_id' => 'required|exists:leads,id', 'agent_id' => 'required|exists:users,id', ]); $lead = Lead::findOrFail($validated['lead_id']); $agent = User::findOrFail($validated['agent_id']); if (! $this->canAssignToAgent($lead, $agent)) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } $lead = $this->assignmentService->assignToAgent( $validated['lead_id'], $validated['agent_id'], auth()->id() ); ActivityLogger::log('lead_owner_changed', "Lead {$lead->id} assigned to user {$agent->id}", $lead); return response()->json($lead->load('assignedAgent')); } public function bulkAssign(Request $request): JsonResponse { $validated = $request->validate([ 'lead_ids' => 'required|array|min:1', 'lead_ids.*' => 'exists:leads,id', 'agent_id' => 'required|exists:users,id', ]); $agent = User::findOrFail($validated['agent_id']); $leads = Lead::whereIn('id', $validated['lead_ids'])->get(); if ($leads->count() !== count(array_unique($validated['lead_ids'])) || $leads->contains(fn (Lead $lead) => ! $this->canAssignToAgent($lead, $agent))) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } $leads = $this->assignmentService->bulkAssign( $validated['lead_ids'], $validated['agent_id'], auth()->id() ); ActivityLogger::log('lead_bulk_owner_changed', count($leads)." leads assigned to user {$agent->id}"); return response()->json(['assigned' => count($leads)]); } public function roundRobin(Request $request): JsonResponse { $validated = $request->validate([ 'lead_ids' => 'required|array|min:1', 'lead_ids.*' => 'exists:leads,id', 'agent_ids' => 'required|array|min:1', 'agent_ids.*' => 'exists:users,id', ]); $agents = User::whereIn('id', $validated['agent_ids'])->get()->keyBy('id'); $leads = Lead::whereIn('id', $validated['lead_ids'])->get(); if ( $agents->count() !== count(array_unique($validated['agent_ids'])) || $leads->count() !== count(array_unique($validated['lead_ids'])) || $leads->contains(fn (Lead $lead) => Gate::denies('assign', $lead)) || $agents->contains(fn (User $agent) => ! $this->agentIsAssignable($agent)) ) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } $leads = $this->assignmentService->roundRobin( $validated['lead_ids'], $validated['agent_ids'], auth()->id() ); ActivityLogger::log('lead_round_robin_owner_changed', count($leads).' leads assigned by round robin'); return response()->json(['assigned' => count($leads)]); } public function reassign(Request $request): JsonResponse { $validated = $request->validate([ 'lead_id' => 'required|exists:leads,id', 'agent_id' => 'required|exists:users,id', ]); $lead = Lead::findOrFail($validated['lead_id']); $agent = User::findOrFail($validated['agent_id']); if (! $this->canAssignToAgent($lead, $agent)) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } $lead = $this->assignmentService->assignToAgent( $validated['lead_id'], $validated['agent_id'], auth()->id() ); ActivityLogger::log('lead_owner_changed', "Lead {$lead->id} reassigned to user {$agent->id}", $lead); return response()->json($lead->load('assignedAgent')); } public function refer(Request $request, Lead $lead): JsonResponse { $validated = $request->validate(['user_id' => 'required|integer|exists:users,id']); $actor = $request->user(); abort_unless(AccessControl::canAccessLead($actor, $lead), 403, 'به این لید دسترسی ندارید.'); if ($actor->hasRole('agent')) { abort_unless($lead->assigned_to === $actor->id, 403, 'فقط لید تحت مسئولیت خود را می‌توانید ارجاع دهید.'); } else { Gate::authorize('assign', $lead); } $target = User::whereKey($validated['user_id'])->where('is_active', true)->firstOrFail(); abort_unless($target->hasAnyRole(['agent', 'supervisor']), 422, 'مقصد ارجاع باید کارشناس یا مدیر فروش باشد.'); if (! $actor->hasRole('admin')) { abort_unless((bool) array_intersect(AccessControl::teamIds($actor), AccessControl::teamIds($target)), 403, 'مقصد ارجاع خارج از تیم شما است.'); } $updated = $this->assignmentService->assignToAgent($lead->id, $target->id, $actor->id); ActivityLogger::log('lead_referred', "Lead {$lead->id} referred from {$actor->id} to {$target->id}", $updated); return response()->json($updated->load('assignedAgent')); } public function returnToPool(int $leadId): JsonResponse { $leadModel = Lead::findOrFail($leadId); Gate::authorize('assign', $leadModel); $lead = $this->assignmentService->returnToPool($leadId); return response()->json($lead); } private function canAssignToAgent(Lead $lead, User $agent): bool { return Gate::allows('assign', $lead) && $this->agentIsAssignable($agent); } private function agentIsAssignable(User $agent): bool { $user = auth()->user(); if (! $agent->hasRole('agent') || ! $agent->is_active) { return false; } if ($user?->hasRole('admin')) { return true; } if ($user?->hasRole('supervisor')) { return (bool) array_intersect(AccessControl::teamIds($user), $agent->teams()->pluck('teams.id')->all()); } return false; } }