374 خطوط
14 KiB
PHP
374 خطوط
14 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Models\Campaign;
|
|
use App\Services\ActivityLogger;
|
|
use App\Services\ReportService;
|
|
use App\Support\AccessControl;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class ReportController extends Controller
|
|
{
|
|
public function __construct(private ReportService $reportService) {}
|
|
|
|
public function agentPerformance(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',
|
|
]);
|
|
|
|
$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<int>
|
|
*/
|
|
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<int>
|
|
*/
|
|
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<int>|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 [];
|
|
}
|
|
}
|