173 خطوط
7.4 KiB
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());
|
|
}
|
|
}
|