user()); $this->applyFilters($query, $request); $direction = $request->get('sort', '-created_at') === 'created_at' ? 'asc' : 'desc'; $paginator = $query->orderBy('created_at', $direction)->paginate(min((int) $request->get('per_page', 15), 100)); return ApiResponse::paginated($paginator, fn (FollowUp $followUp) => $this->resource($followUp)); } public function store(Request $request): JsonResponse { $validated = $request->validate([ 'lead_id' => 'required|exists:leads,id', 'user_id' => 'nullable|exists:users,id', 'call_id' => 'nullable|exists:calls,id', 'scheduled_at' => 'required|date', 'notes' => 'nullable|string', ]); $lead = Lead::findOrFail($validated['lead_id']); Gate::authorize('create', [FollowUp::class, $lead]); $assigneeId = (int) ($validated['user_id'] ?? $request->user()->id); abort_unless(AccessControl::canAssignUser($request->user(), $assigneeId), 403, 'مسئول انتخاب‌شده خارج از محدوده مجاز است.'); $followUp = $this->service->schedule( $lead, $assigneeId, $validated['scheduled_at'], $request->user(), $validated['notes'] ?? null, $validated['call_id'] ?? null, ); return ApiResponse::success($this->resource($followUp), 201, 'پیگیری ایجاد شد.'); } public function show(FollowUp $followUp): JsonResponse { Gate::authorize('view', $followUp); return ApiResponse::success($this->resource($followUp->load('lead:id,first_name,last_name,company,phone', 'user:id,name', 'creator:id,name'))); } public function update(Request $request, FollowUp $followUp): JsonResponse { Gate::authorize('update', $followUp); $validated = $request->validate([ 'user_id' => 'sometimes|exists:users,id', 'scheduled_at' => 'sometimes|date', 'notes' => 'nullable|string', 'status' => 'sometimes|string|in:pending,completed,cancelled', ]); if (isset($validated['user_id'])) { abort_unless(AccessControl::canAssignUser($request->user(), (int) $validated['user_id']), 403, 'مسئول انتخاب‌شده خارج از محدوده مجاز است.'); } $followUp = $this->service->update($followUp, $validated); return ApiResponse::success($this->resource($followUp), message: 'پیگیری ویرایش شد.'); } public function markDone(FollowUp $followUp): JsonResponse { Gate::authorize('complete', $followUp); $followUp->update(['status' => 'completed', 'completed_at' => now(), 'is_overdue' => false]); ActivityLogger::log('follow_up_completed', "Follow-up {$followUp->id} completed", $followUp); return ApiResponse::success($this->resource($followUp->fresh(['lead:id,first_name,last_name,company,phone', 'user:id,name', 'creator:id,name'])), message: 'پیگیری تکمیل شد.'); } public function destroy(FollowUp $followUp): JsonResponse { Gate::authorize('delete', $followUp); $followUp->delete(); ActivityLogger::log('follow_up_deleted', "Follow-up {$followUp->id} deleted", $followUp); return ApiResponse::success(null, message: 'پیگیری حذف شد.'); } public function today(Request $request): JsonResponse { Gate::authorize('viewAny', FollowUp::class); $query = FollowUp::with('lead:id,first_name,last_name,company,phone', 'user:id,name', 'creator:id,name') ->whereDate('scheduled_at', now()->toDateString()) ->where('status', 'pending'); AccessControl::scopeFollowUps($query, $request->user()); $direction = $request->get('sort', '-created_at') === 'created_at' ? 'asc' : 'desc'; $paginator = $query->orderBy('created_at', $direction)->paginate(min((int) $request->get('per_page', 50), 100)); return ApiResponse::paginated($paginator, fn (FollowUp $followUp) => $this->resource($followUp)); } public function overdue(Request $request): JsonResponse { Gate::authorize('viewAny', FollowUp::class); $query = FollowUp::with('lead:id,first_name,last_name,company,phone', 'user:id,name', 'creator:id,name') ->where('status', 'pending') ->where(fn ($overdue) => $overdue->where('is_overdue', true)->orWhere('scheduled_at', '<', now())); AccessControl::scopeFollowUps($query, $request->user()); $direction = $request->get('sort', '-created_at') === 'created_at' ? 'asc' : 'desc'; $paginator = $query->orderBy('created_at', $direction)->paginate(min((int) $request->get('per_page', 50), 100)); return ApiResponse::paginated($paginator, fn (FollowUp $followUp) => $this->resource($followUp)); } private function applyFilters($query, Request $request): void { if ($request->filled('status')) { $query->where('status', $request->get('status')); } if ($request->filled('lead_id')) { $query->where('lead_id', $request->integer('lead_id')); } if ($request->filled('user_id')) { $query->where('user_id', $request->integer('user_id')); } if ($request->filled('date_from')) { $query->whereDate('scheduled_at', '>=', $request->get('date_from')); } if ($request->filled('date_to')) { $query->whereDate('scheduled_at', '<=', $request->get('date_to')); } } private function resource(FollowUp $followUp): array { return (new FollowUpResource($followUp))->resolve(request()); } }