validate([ 'agent_id' => 'nullable|exists:users,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $allowedAgentIds = $this->allowedAgentIds(); if (!empty($validated['agent_id']) && !in_array((int) $validated['agent_id'], $allowedAgentIds, true)) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } if (empty($validated['agent_id'])) { $agents = User::role('agent')->whereIn('id', $allowedAgentIds)->orderBy('name')->get(); return response()->json($agents->map(fn(User $agent) => $this->reportService->agentPerformance( $agent->id, $validated['date_from'] ?? null, $validated['date_to'] ?? null ))->values()); } return response()->json( $this->reportService->agentPerformance( $validated['agent_id'], $validated['date_from'] ?? null, $validated['date_to'] ?? null ) ); } public function teamPerformance(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate([ 'team_id' => 'nullable|exists:teams,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $allowedTeamIds = $this->allowedTeamIds(); if (!empty($validated['team_id']) && !in_array((int) $validated['team_id'], $allowedTeamIds, true)) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } if (empty($validated['team_id'])) { $teams = Team::whereIn('id', $allowedTeamIds)->orderBy('name')->get(); return response()->json($teams->map(fn(Team $team) => $this->reportService->teamPerformance( $team->id, $validated['date_from'] ?? null, $validated['date_to'] ?? null ))->values()); } return response()->json( $this->reportService->teamPerformance( $validated['team_id'], $validated['date_from'] ?? null, $validated['date_to'] ?? null ) ); } public function campaignReport(Request $request, int $campaignId): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate([ 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $campaign = Campaign::findOrFail($campaignId); Gate::authorize('view', $campaign); return response()->json($this->reportService->campaignReport( $campaignId, $validated['date_from'] ?? null, $validated['date_to'] ?? null )); } public function conversionReport(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate([ 'agent_id' => 'nullable|exists:users,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } return response()->json( $this->reportService->conversionReport( $validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds ) ); } public function callReport(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate([ 'agent_id' => 'nullable|exists:users,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } return response()->json( $this->reportService->callReport( $validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds ) ); } public function followUpReport(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate([ 'agent_id' => 'nullable|exists:users,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } return response()->json( $this->reportService->followUpReport( $validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds ) ); } public function lostReasonReport(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $this->validateScopedReport($request); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) return response()->json(['message' => 'دسترسی غیرمجاز'], 403); return response()->json($this->reportService->lostReasonReport($validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds)); } public function sourcePerformance(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $this->validateScopedReport($request); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) return response()->json(['message' => 'دسترسی غیرمجاز'], 403); return response()->json($this->reportService->sourcePerformanceReport($validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds)); } public function duplicateLeads(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $this->validateScopedReport($request); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) return response()->json(['message' => 'دسترسی غیرمجاز'], 403); return response()->json($this->reportService->duplicateLeadsReport($validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds)); } public function importQuality(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $request->validate(['date_from' => 'nullable|date', 'date_to' => 'nullable|date']); return response()->json($this->reportService->importQualityReport($validated['date_from'] ?? null, $validated['date_to'] ?? null)); } public function callQuality(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $this->validateScopedReport($request); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) return response()->json(['message' => 'دسترسی غیرمجاز'], 403); return response()->json($this->reportService->callQualityReport($validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds)); } public function bestContactTime(Request $request): JsonResponse { Gate::authorize('view-report-data'); $validated = $this->validateScopedReport($request); $agentIds = $this->requestedAgentScope($validated['agent_id'] ?? null); if ($agentIds === false) return response()->json(['message' => 'دسترسی غیرمجاز'], 403); return response()->json($this->reportService->bestContactTimeReport($validated['date_from'] ?? null, $validated['date_to'] ?? null, $validated['agent_id'] ?? null, $agentIds)); } public function exportExcel(Request $request): StreamedResponse|JsonResponse { Gate::authorize('export-report-data'); $type = $request->type ?? 'agent'; $agentIds = $this->requestedAgentScope($request->integer('agent_id') ?: null); if ($agentIds === false) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } if ($request->filled('team_id') && !in_array($request->integer('team_id'), $this->allowedTeamIds(), true)) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } ActivityLogger::log('report_exported', "Report export requested: {$type}"); $data = match ($type) { 'agent' => $this->reportService->agentPerformance($request->integer('agent_id') ?: auth()->id(), $request->date_from, $request->date_to), 'team' => $this->reportService->teamPerformance($request->integer('team_id') ?: ($this->allowedTeamIds()[0] ?? 0), $request->date_from, $request->date_to), 'campaign' => $request->integer('campaign_id') ? $this->reportService->campaignReport($request->integer('campaign_id'), $request->date_from, $request->date_to) : [], 'conversion' => $this->reportService->conversionReport($request->date_from, $request->date_to, null, $agentIds), 'call' => $this->reportService->callReport($request->date_from, $request->date_to, null, $agentIds), 'follow_up' => $this->reportService->followUpReport($request->date_from, $request->date_to, null, $agentIds), 'lost_reason' => $this->reportService->lostReasonReport($request->date_from, $request->date_to, null, $agentIds), 'source' => $this->reportService->sourcePerformanceReport($request->date_from, $request->date_to, null, $agentIds), 'duplicate' => $this->reportService->duplicateLeadsReport($request->date_from, $request->date_to, null, $agentIds), 'import_quality' => $this->reportService->importQualityReport($request->date_from, $request->date_to), 'call_quality' => $this->reportService->callQualityReport($request->date_from, $request->date_to, null, $agentIds), 'best_contact_time' => $this->reportService->bestContactTimeReport($request->date_from, $request->date_to, null, $agentIds), default => [], }; $rows = $this->flattenForCsv($data); $filename = 'report-' . $type . '-' . now()->format('Ymd-His') . '.csv'; return response()->streamDownload(function () use ($rows) { $out = fopen('php://output', 'w'); fwrite($out, "\xEF\xBB\xBF"); if ($rows === []) { fputcsv($out, ['message'], ','); fputcsv($out, ['داده‌ای برای خروجی وجود ندارد'], ','); fclose($out); return; } fputcsv($out, array_keys($rows[0]), ','); foreach ($rows as $row) { fputcsv($out, array_map(fn($value) => is_scalar($value) || $value === null ? $value : json_encode($value, JSON_UNESCAPED_UNICODE), $row), ','); } fclose($out); }, $filename, ['Content-Type' => 'text/csv; charset=UTF-8']); } private function validateScopedReport(Request $request): array { return $request->validate([ 'agent_id' => 'nullable|exists:users,id', 'date_from' => 'nullable|date', 'date_to' => 'nullable|date', ]); } /** * @return list */ private function allowedAgentIds(): array { $user = auth()->user(); if ($user->hasRole('admin')) { return User::role('agent')->pluck('id')->all(); } if ($user->hasRole('supervisor')) { return AccessControl::teamMemberIds($user); } return [$user->id]; } /** * @return list */ private function allowedTeamIds(): array { $user = auth()->user(); if ($user->hasRole('admin')) { return Team::pluck('id')->all(); } if ($user->hasRole('supervisor')) { return AccessControl::teamIds($user); } return []; } /** * @return list|false */ private function requestedAgentScope(?int $agentId): array|false { $allowedAgentIds = $this->allowedAgentIds(); if ($agentId) { return in_array($agentId, $allowedAgentIds, true) ? [$agentId] : false; } return auth()->user()->hasRole('admin') ? [] : $allowedAgentIds; } private function flattenForCsv(mixed $data): array { if ($data instanceof \Illuminate\Support\Collection) { $data = $data->toArray(); } if ($data instanceof \Illuminate\Database\Eloquent\Model) { $data = $data->toArray(); } if (is_array($data) && array_is_list($data)) { return array_map(fn($row) => is_array($row) ? $row : ['value' => $row], $data); } if (is_array($data)) { foreach (['agents', 'by_stage', 'by_result', 'items', 'by_reason', 'sources', 'phone_duplicates', 'batches', 'by_agent', 'by_hour'] as $key) { if (isset($data[$key]) && is_iterable($data[$key])) { return collect($data[$key])->map(fn($row) => $row instanceof \Illuminate\Database\Eloquent\Model ? $row->toArray() : (array) $row)->values()->all(); } } return [$data]; } return []; } }