CRM/backend/app/Http/Controllers/Api/FollowUpController.php

153 خطوط
6.4 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\FollowUpResource;
use App\Http\Responses\ApiResponse;
use App\Models\FollowUp;
use App\Models\Lead;
use App\Services\ActivityLogger;
use App\Services\FollowUpService;
use App\Support\AccessControl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class FollowUpController extends Controller
{
public function __construct(private FollowUpService $service) {}
public function index(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');
AccessControl::scopeFollowUps($query, $request->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());
}
}