whereDate('created_at', '>=', $dateFrom); $leadsQuery->whereDate('created_at', '>=', $dateFrom); } if ($dateTo) { $callsQuery->whereDate('created_at', '<=', $dateTo); $leadsQuery->whereDate('created_at', '<=', $dateTo); } $totalCalls = (clone $callsQuery)->count(); $answeredCalls = (clone $callsQuery)->whereIn('result', $this->successfulResultNames())->count(); $totalLeads = (clone $leadsQuery)->count(); $wonLeads = (clone $leadsQuery)->where('final_result', 'موفق')->count(); return [ 'agent' => $agent->only(['id', 'name', 'email']), 'total_calls' => $totalCalls, 'answered_calls' => $answeredCalls, 'no_answer_calls' => $totalCalls - $answeredCalls, 'answer_rate' => $totalCalls > 0 ? round(($answeredCalls / $totalCalls) * 100, 1) : 0, 'total_leads_assigned' => $totalLeads, 'won_sales' => $wonLeads, 'conversion_rate' => $totalLeads > 0 ? round(($wonLeads / $totalLeads) * 100, 1) : 0, 'avg_call_duration' => (clone $callsQuery)->avg('duration') ?? 0, ]; } public function teamPerformance(int $teamId, ?string $dateFrom = null, ?string $dateTo = null): array { $team = Team::with('members')->findOrFail($teamId); $agentIds = $team->members->pluck('id')->toArray(); $totalCalls = Call::whereIn('user_id', $agentIds); $totalLeads = Lead::whereIn('assigned_to', $agentIds); if ($dateFrom) { $totalCalls->whereDate('created_at', '>=', $dateFrom); $totalLeads->whereDate('created_at', '>=', $dateFrom); } if ($dateTo) { $totalCalls->whereDate('created_at', '<=', $dateTo); $totalLeads->whereDate('created_at', '<=', $dateTo); } $wonLeads = (clone $totalLeads)->where('final_result', 'موفق')->count(); $agentPerformance = []; foreach ($team->members as $member) { $agentPerformance[] = $this->agentPerformance($member->id, $dateFrom, $dateTo); } return [ 'team' => $team->only(['id', 'name']), 'member_count' => count($agentIds), 'total_calls' => $totalCalls->count(), 'total_leads' => $totalLeads->count(), 'won_sales' => $wonLeads, 'conversion_rate' => $totalLeads->count() > 0 ? round(($wonLeads / $totalLeads->count()) * 100, 1) : 0, 'agents' => $agentPerformance, ]; } public function campaignReport(int $campaignId, ?string $dateFrom = null, ?string $dateTo = null): array { $campaign = \App\Models\Campaign::with('assignedAgents')->findOrFail($campaignId); $leads = Lead::where('campaign_id', $campaignId); if ($dateFrom) { $leads->whereDate('created_at', '>=', $dateFrom); } if ($dateTo) { $leads->whereDate('created_at', '<=', $dateTo); } $leadIds = (clone $leads)->pluck('id'); $calls = Call::whereIn('lead_id', $leadIds); if ($dateFrom) { $calls->whereDate('created_at', '>=', $dateFrom); } if ($dateTo) { $calls->whereDate('created_at', '<=', $dateTo); } $wonLeads = (clone $leads)->where('final_result', 'موفق')->count(); $totalLeads = (clone $leads)->count(); return [ 'campaign' => $campaign->only(['id', 'name', 'target', 'start_date', 'end_date', 'status']), 'total_leads' => $totalLeads, 'assigned_leads' => (clone $leads)->where('is_unassigned', false)->count(), 'total_calls' => (clone $calls)->count(), 'won_sales' => $wonLeads, 'target_progress' => $campaign->target > 0 ? round(($wonLeads / $campaign->target) * 100, 1) : 0, 'conversion_rate' => $totalLeads > 0 ? round(($wonLeads / $totalLeads) * 100, 1) : 0, ]; } public function conversionReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $leadsQuery = Lead::query(); if ($dateFrom) $leadsQuery->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $leadsQuery->whereDate('created_at', '<=', $dateTo); if ($agentId) $leadsQuery->where('assigned_to', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $leadsQuery->whereIn('assigned_to', $agentIds); elseif (is_array($agentIds)) $leadsQuery->whereRaw('1 = 0'); $total = (clone $leadsQuery)->count(); $won = (clone $leadsQuery)->where('final_result', 'موفق')->count(); $lost = (clone $leadsQuery)->where('final_result', 'ناموفق')->count(); $today = Carbon::today(); $now = Carbon::now(); $stages = \App\Models\PipelineStage::orderBy('sort_order')->get(); $byStage = []; foreach ($stages as $stage) { $count = (clone $leadsQuery)->where('pipeline_stage_id', $stage->id)->count(); $byStage[] = [ 'name' => $stage->name, 'color' => $stage->color, 'count' => $count, 'percentage' => $total > 0 ? round(($count / $total) * 100, 1) : 0, ]; } return [ 'total_leads' => $total, 'by_stage' => $byStage, 'conversion_rate' => $total > 0 ? round(($won / $total) * 100, 1) : 0, 'won_leads' => $won, 'lost_leads' => $lost, 'follow_ups_due_today' => $this->filterFollowUpsByAgent( FollowUp::whereDate('scheduled_at', $today)->where('status', 'pending'), $agentId, $agentIds )->count(), 'overdue_follow_ups' => $this->filterFollowUpsByAgent( FollowUp::where('scheduled_at', '<', $now)->where('status', 'pending'), $agentId, $agentIds )->count(), 'lost_reason_analysis' => (clone $leadsQuery) ->where('final_result', 'ناموفق') ->whereNotNull('lost_reason') ->selectRaw('lost_reason as name, count(*) as count') ->groupBy('lost_reason') ->orderByDesc('count') ->get(), 'proposal_sent_not_closed' => (clone $leadsQuery) ->whereHas('pipelineStage', fn($q) => $q->where('slug', 'proposal_sent')) ->whereNull('final_result') ->count(), ]; } public function callReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = Call::query(); if ($dateFrom) $query->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('created_at', '<=', $dateTo); if ($agentId) $query->where('user_id', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $query->whereIn('user_id', $agentIds); elseif (is_array($agentIds)) $query->whereRaw('1 = 0'); $total = $query->count(); $results = \App\Models\CallResult::all(); $byResult = []; foreach ($results as $result) { $count = (clone $query)->where('result', $result->name)->count(); $byResult[] = [ 'name' => $result->name, 'color' => $result->color, 'count' => $count, 'percentage' => $total > 0 ? round(($count / $total) * 100, 1) : 0, ]; } return [ 'total_calls' => $total, 'avg_duration' => $query->avg('duration') ?? 0, 'by_result' => $byResult, ]; } public function followUpReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = FollowUp::query(); if ($dateFrom) $query->whereDate('scheduled_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('scheduled_at', '<=', $dateTo); if ($agentId) $query->where('user_id', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $query->whereIn('user_id', $agentIds); elseif (is_array($agentIds)) $query->whereRaw('1 = 0'); return [ 'total' => $query->count(), 'pending' => (clone $query)->where('status', 'pending')->count(), 'completed' => (clone $query)->where('status', 'completed')->count(), 'overdue' => (clone $query)->where('is_overdue', true)->count(), 'items' => (clone $query)->with(['user:id,name', 'lead:id,company,first_name,last_name']) ->where(fn($q) => $q->where('is_overdue', true)->orWhere('scheduled_at', '<', Carbon::now())) ->latest('scheduled_at') ->limit(50) ->get(), ]; } public function lostReasonReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = $this->scopedLeads($dateFrom, $dateTo, $agentId, $agentIds) ->where('final_result', 'ناموفق') ->whereNotNull('lost_reason'); $total = (clone $query)->count(); return [ 'total_lost' => $total, 'by_reason' => (clone $query)->selectRaw('lost_reason as name, count(*) as count') ->groupBy('lost_reason') ->orderByDesc('count') ->get() ->map(fn($row) => [ 'name' => $row->name, 'count' => (int) $row->count, 'percentage' => $total > 0 ? round(($row->count / $total) * 100, 1) : 0, ])->values(), ]; } public function sourcePerformanceReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = $this->scopedLeads($dateFrom, $dateTo, $agentId, $agentIds); return [ 'sources' => (clone $query) ->selectRaw("COALESCE(source, 'نامشخص') as source, count(*) as total, sum(case when final_result = 'موفق' then 1 else 0 end) as won, sum(case when final_result = 'ناموفق' then 1 else 0 end) as lost") ->groupBy('source') ->orderByDesc('total') ->get() ->map(fn($row) => [ 'source' => $row->source, 'total' => (int) $row->total, 'won' => (int) $row->won, 'lost' => (int) $row->lost, 'conversion_rate' => $row->total > 0 ? round(($row->won / $row->total) * 100, 1) : 0, ])->values(), ]; } public function duplicateLeadsReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = $this->scopedLeads($dateFrom, $dateTo, $agentId, $agentIds); $duplicatePhones = (clone $query) ->whereNotNull('phone') ->selectRaw('phone, count(*) as count') ->groupBy('phone') ->havingRaw('count(*) > 1') ->orderByDesc('count') ->limit(50) ->get(); $duplicateEmails = (clone $query) ->whereNotNull('email') ->selectRaw('email, count(*) as count') ->groupBy('email') ->havingRaw('count(*) > 1') ->orderByDesc('count') ->limit(50) ->get(); $duplicateCompanies = (clone $query) ->whereNotNull('company') ->selectRaw('company, count(*) as count') ->groupBy('company') ->havingRaw('count(*) > 1') ->orderByDesc('count') ->limit(50) ->get(); return [ 'phone_duplicates' => $duplicatePhones, 'email_duplicates' => $duplicateEmails, 'company_duplicates' => $duplicateCompanies, 'merge_history_count' => MergeHistory::where('entity_type', 'like', '%Lead%')->count(), ]; } public function importQualityReport(?string $dateFrom = null, ?string $dateTo = null): array { $query = ImportBatch::with(['user:id,name', 'campaign:id,name']); if ($dateFrom) $query->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('created_at', '<=', $dateTo); $batches = $query->latest()->limit(50)->get(); $totalRows = max($batches->sum('total_rows'), 1); return [ 'total_batches' => $batches->count(), 'total_rows' => $batches->sum('total_rows'), 'imported_rows' => $batches->sum('imported_rows'), 'failed_rows' => $batches->sum('failed_rows'), 'skipped_rows' => $batches->sum('skipped_rows'), 'success_rate' => round(($batches->sum('imported_rows') / $totalRows) * 100, 1), 'batches' => $batches, ]; } public function callQualityReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = QualityReview::with(['agent:id,name', 'reviewer:id,name']); if ($dateFrom) $query->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('created_at', '<=', $dateTo); if ($agentId) $query->where('agent_id', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $query->whereIn('agent_id', $agentIds); elseif (is_array($agentIds)) $query->whereRaw('1 = 0'); return [ 'reviewed_calls' => (clone $query)->count(), 'average_score' => round((float) ((clone $query)->avg('overall_score') ?? 0), 1), 'low_score_count' => (clone $query)->where('overall_score', '<', 60)->count(), 'by_agent' => (clone $query)->selectRaw('agent_id, count(*) as reviews, avg(overall_score) as average_score') ->groupBy('agent_id') ->with('agent:id,name') ->get(), 'recent_reviews' => (clone $query)->latest()->limit(20)->get(), ]; } public function bestContactTimeReport(?string $dateFrom = null, ?string $dateTo = null, ?int $agentId = null, ?array $agentIds = null): array { $query = Call::query(); if ($dateFrom) $query->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('created_at', '<=', $dateTo); if ($agentId) $query->where('user_id', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $query->whereIn('user_id', $agentIds); elseif (is_array($agentIds)) $query->whereRaw('1 = 0'); $total = (clone $query)->count(); if ($total < 20) { return [ 'enough_data' => false, 'message' => 'برای تحلیل بهترین زمان تماس، حداقل ۲۰ تماس ثبت‌شده لازم است.', 'total_calls' => $total, 'by_hour' => [], ]; } return [ 'enough_data' => true, 'total_calls' => $total, 'by_hour' => (clone $query)->get(['created_at', 'result']) ->groupBy(fn(Call $call) => $call->created_at->hour) ->sortKeys() ->map(function ($calls, int $hour) { $successful = $calls->whereIn('result', $this->successfulResultNames())->count(); return [ 'hour' => $hour, 'total' => $calls->count(), 'successful' => $successful, 'success_rate' => $calls->count() > 0 ? round(($successful / $calls->count()) * 100, 1) : 0, ]; })->values(), ]; } private function filterFollowUpsByAgent($query, ?int $agentId, ?array $agentIds = null) { if ($agentId) { $query->where('user_id', $agentId); } elseif (is_array($agentIds) && $agentIds !== []) { $query->whereIn('user_id', $agentIds); } elseif (is_array($agentIds)) { $query->whereRaw('1 = 0'); } return $query; } private function scopedLeads(?string $dateFrom, ?string $dateTo, ?int $agentId, ?array $agentIds) { $query = Lead::query(); if ($dateFrom) $query->whereDate('created_at', '>=', $dateFrom); if ($dateTo) $query->whereDate('created_at', '<=', $dateTo); if ($agentId) $query->where('assigned_to', $agentId); elseif (is_array($agentIds) && $agentIds !== []) $query->whereIn('assigned_to', $agentIds); elseif (is_array($agentIds)) $query->whereRaw('1 = 0'); return $query; } private function successfulResultNames(): array { return CallResult::where('is_positive', true)->pluck('name')->all(); } }