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

173 خطوط
7.4 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\CampaignResource;
use App\Http\Responses\ApiResponse;
use App\Models\Campaign;
use App\Models\User;
use App\Services\ActivityLogger;
use App\Support\AccessControl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
class CampaignController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Campaign::with('assignedAgents:id,name', 'assignedSupervisors:id,name', 'salesScript:id,title', 'product:id,name,base_price')
->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());
}
}