validate([ 'from' => 'required|date', 'to' => 'required|date|after_or_equal:from', 'type' => 'nullable|string|in:task,follow_up', ]); $from = Carbon::parse($validated['from'])->startOfDay(); $to = Carbon::parse($validated['to'])->endOfDay(); abort_if($from->diffInDays($to) > 370, 422, 'بازه تقویم نمی‌تواند بیشتر از یک سال باشد.'); $events = collect(); $type = $validated['type'] ?? null; if ((! $type || $type === 'task') && Gate::allows('viewAny', Task::class)) { $tasks = Task::query() ->with(['assignee:id,name', 'taskable']) ->whereNotNull('due_at') ->whereBetween('due_at', [$from, $to]); AccessControl::scopeTasks($tasks, $request->user()); $events->push(...$tasks->get()->map(fn (Task $task) => [ 'id' => "task-{$task->id}", 'entity_id' => $task->id, 'type' => 'task', 'title' => $task->subject, 'starts_at' => $task->due_at, 'status' => is_object($task->status) ? $task->status->value : $task->status, 'priority' => is_object($task->priority) ? $task->priority->value : $task->priority, 'assignee' => $task->assignee?->only(['id', 'name']), 'related' => $task->taskable ? [ 'type' => class_basename($task->taskable_type), 'id' => $task->taskable_id, 'label' => $task->taskable->name ?? $task->taskable->title ?? $task->taskable->company ?? null, ] : null, 'url' => "/tasks?task={$task->id}", ])); } if ((! $type || $type === 'follow_up') && Gate::allows('viewAny', FollowUp::class)) { $followUps = FollowUp::query() ->with(['lead:id,first_name,last_name,company', 'user:id,name']) ->whereBetween('scheduled_at', [$from, $to]); AccessControl::scopeFollowUps($followUps, $request->user()); $events->push(...$followUps->get()->map(fn (FollowUp $followUp) => [ 'id' => "follow-up-{$followUp->id}", 'entity_id' => $followUp->id, 'type' => 'follow_up', 'title' => $followUp->notes ?: 'پیگیری لید', 'starts_at' => $followUp->scheduled_at, 'status' => $followUp->status, 'priority' => null, 'assignee' => $followUp->user?->only(['id', 'name']), 'related' => $followUp->lead ? [ 'type' => 'Lead', 'id' => $followUp->lead_id, 'label' => $followUp->lead->company ?: trim("{$followUp->lead->first_name} {$followUp->lead->last_name}"), ] : null, 'url' => "/follow-ups?follow_up={$followUp->id}", ])); } abort_if($events->isEmpty() && ! Gate::allows('viewAny', Task::class) && ! Gate::allows('viewAny', FollowUp::class), 403); return response()->json(['events' => $events->sortBy('starts_at')->values()]); } }