withCount([ 'leads', 'leads as contacted_leads_count' => fn ($leadQuery) => $leadQuery->whereNotNull('last_call_at'), 'leads as won_leads_count' => fn ($leadQuery) => $leadQuery->where('final_result', 'موفق'), ]) ->withSum([ 'leads as won_value_sum' => fn ($leadQuery) => $leadQuery->where('final_result', 'موفق'), ], 'deal_value'); Gate::authorize('viewAny', Campaign::class); AccessControl::scopeCampaigns($query, auth()->user()); if ($request->search) { $query->where('name', 'like', "%{$request->search}%"); } if ($request->status) { $query->where('status', $request->status); } $paginator = $query->orderByDesc('created_at')->paginate(min((int) $request->get('per_page', 15), 100)); return ApiResponse::paginated($paginator, fn (Campaign $campaign) => $this->resource($campaign)); } public function store(Request $request): JsonResponse { Gate::authorize('create', Campaign::class); $validated = $request->validate([ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'product_service' => 'nullable|string|max:255', 'product_id' => 'nullable|exists:products,id', 'channel' => 'nullable|string|in:phone,sms,email,social,advertising,event,referral,other', 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', 'target' => 'nullable|integer|min:0', 'budget' => 'nullable|numeric|min:0', 'actual_cost' => 'nullable|numeric|min:0', 'status' => 'nullable|string|in:draft,active,paused,completed,archived', 'sales_script_id' => 'nullable|exists:sales_scripts,id', 'agent_ids' => 'nullable|array', 'agent_ids.*' => 'exists:users,id', 'supervisor_ids' => 'nullable|array', 'supervisor_ids.*' => 'exists:users,id', ]); $this->authorizeAssignments($validated); $campaign = DB::transaction(function () use ($validated): Campaign { $campaign = Campaign::create(collect($validated)->except(['agent_ids', 'supervisor_ids'])->all()); $this->syncAssignments($campaign, $validated['agent_ids'] ?? [], $validated['supervisor_ids'] ?? []); return $campaign; }); ActivityLogger::log('campaign_created', "Campaign {$campaign->name} created", $campaign); return ApiResponse::success($this->resource($campaign->load('assignedAgents', 'assignedSupervisors', 'salesScript', 'product')), 201, 'کمپین ایجاد شد.'); } public function show(Campaign $campaign): JsonResponse { Gate::authorize('view', $campaign); return ApiResponse::success($this->resource( $campaign->load([ 'assignedAgents', 'assignedSupervisors', 'salesScript', 'product', 'leads' => fn ($q) => $q->with('leadStatus'), ]) )); } public function update(Request $request, Campaign $campaign): JsonResponse { Gate::authorize('update', $campaign); $validated = $request->validate([ 'name' => 'sometimes|string|max:255', 'description' => 'nullable|string', 'product_service' => 'nullable|string|max:255', 'product_id' => 'nullable|exists:products,id', 'channel' => 'nullable|string|in:phone,sms,email,social,advertising,event,referral,other', 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', 'target' => 'nullable|integer|min:0', 'budget' => 'nullable|numeric|min:0', 'actual_cost' => 'nullable|numeric|min:0', 'status' => 'nullable|string|in:draft,active,paused,completed,archived', 'sales_script_id' => 'nullable|exists:sales_scripts,id', 'agent_ids' => 'nullable|array', 'agent_ids.*' => 'exists:users,id', 'supervisor_ids' => 'nullable|array', 'supervisor_ids.*' => 'exists:users,id', ]); $this->authorizeAssignments($validated); DB::transaction(function () use ($campaign, $validated, $request): void { $campaign->update(collect($validated)->except(['agent_ids', 'supervisor_ids'])->all()); if ($request->has('agent_ids') || $request->has('supervisor_ids')) { $agentIds = $request->has('agent_ids') ? ($validated['agent_ids'] ?? []) : $campaign->assignedAgents()->pluck('users.id')->all(); $supervisorIds = $request->has('supervisor_ids') ? ($validated['supervisor_ids'] ?? []) : $campaign->assignedSupervisors()->pluck('users.id')->all(); $this->syncAssignments($campaign, $agentIds, $supervisorIds); } }); ActivityLogger::log('campaign_updated', "Campaign {$campaign->name} updated", $campaign); return ApiResponse::success($this->resource($campaign->load('assignedAgents', 'assignedSupervisors', 'salesScript', 'product')), message: 'کمپین ویرایش شد.'); } public function destroy(Campaign $campaign): JsonResponse { Gate::authorize('delete', $campaign); $campaign->delete(); ActivityLogger::log('campaign_deleted', "Campaign {$campaign->name} deleted"); return ApiResponse::success(null, message: 'کمپین حذف شد.'); } private function authorizeAssignments(array $validated): void { foreach (['agent_ids' => 'agent', 'supervisor_ids' => 'supervisor'] as $key => $role) { foreach ($validated[$key] ?? [] as $userId) { $user = User::findOrFail($userId); abort_unless($user->hasRole($role), 422, 'نقش کاربر انتخاب‌شده با نوع تخصیص سازگار نیست.'); abort_unless(AccessControl::canAssignUser(auth()->user(), $user->id), 403, 'کاربر انتخاب‌شده خارج از محدوده تیم شما است.'); } } } private function syncAssignments(Campaign $campaign, array $agentIds, array $supervisorIds): void { $pivot = []; foreach ($agentIds as $id) { $pivot[$id] = ['role' => 'agent']; } foreach ($supervisorIds as $id) { $pivot[$id] = ['role' => 'supervisor']; } $campaign->assignedAgents()->newPivotQuery()->where('campaign_id', $campaign->id)->delete(); if ($pivot) { $campaign->assignedAgents()->attach($pivot); } } private function resource(Campaign $campaign): array { return (new CampaignResource($campaign))->resolve(request()); } }