198 خطوط
8.9 KiB
PHP
198 خطوط
8.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Campaign;
|
|
use App\Models\Product;
|
|
use App\Models\SalesScript;
|
|
use App\Models\User;
|
|
use App\Services\ActivityLogger;
|
|
use App\Services\NotificationService;
|
|
use App\Support\AccessControl;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Gate;
|
|
|
|
class ScriptController extends Controller
|
|
{
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
Gate::authorize('viewAny', SalesScript::class);
|
|
$query = SalesScript::with('sections', 'campaign:id,name,sales_script_id', 'product:id,name,sales_script_id', 'assignees:id,name');
|
|
if (! $request->user()->can('manage_scripts')) {
|
|
$query->where('is_active', true)->whereHas('assignees', fn ($users) => $users->whereKey($request->user()->id));
|
|
}
|
|
foreach (['category', 'lead_source'] as $filter) {
|
|
if ($request->filled($filter)) {
|
|
$query->where($filter, $request->string($filter));
|
|
}
|
|
}
|
|
if ($request->filled('search')) {
|
|
$search = $request->string('search');
|
|
$query->where(fn ($scope) => $scope->where('title', 'like', "%{$search}%")->orWhere('description', 'like', "%{$search}%"));
|
|
}
|
|
|
|
return response()->json($query->latest()->paginate(min((int) $request->get('per_page', 15), 100)));
|
|
}
|
|
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
Gate::authorize('create', SalesScript::class);
|
|
$validated = $request->validate($this->rules());
|
|
$this->authorizeParents($validated);
|
|
$this->authorizeAssignees($validated['assigned_user_ids'] ?? [], $request->user());
|
|
|
|
$script = DB::transaction(function () use ($validated, $request): SalesScript {
|
|
$script = SalesScript::create(collect($validated)->except(['sections', 'campaign_id', 'product_id', 'assigned_user_ids'])->all());
|
|
$this->syncSections($script, $validated['sections'] ?? []);
|
|
$this->syncParents($script, $validated);
|
|
$this->syncAssignees($script, $validated['assigned_user_ids'] ?? [], $request->user());
|
|
|
|
return $script;
|
|
});
|
|
|
|
ActivityLogger::log('script_created', "Script {$script->title} created", $script);
|
|
|
|
return response()->json($script->load('sections', 'campaign:id,name,sales_script_id', 'product:id,name,sales_script_id', 'assignees:id,name'), 201);
|
|
}
|
|
|
|
public function show(SalesScript $script): JsonResponse
|
|
{
|
|
Gate::authorize('view', $script);
|
|
|
|
return response()->json($script->load('sections', 'campaign:id,name,sales_script_id', 'product:id,name,sales_script_id', 'assignees:id,name'));
|
|
}
|
|
|
|
public function update(Request $request, SalesScript $script): JsonResponse
|
|
{
|
|
Gate::authorize('update', $script);
|
|
$validated = $request->validate($this->rules(partial: true));
|
|
$this->authorizeParents($validated);
|
|
if ($request->has('assigned_user_ids')) {
|
|
$this->authorizeAssignees($validated['assigned_user_ids'] ?? [], $request->user());
|
|
}
|
|
|
|
DB::transaction(function () use ($script, $validated, $request): void {
|
|
$script->update(collect($validated)->except(['sections', 'campaign_id', 'product_id', 'assigned_user_ids'])->all());
|
|
if ($request->has('sections')) {
|
|
$this->syncSections($script, $validated['sections'] ?? []);
|
|
}
|
|
if ($request->has('campaign_id') || $request->has('product_id')) {
|
|
$this->syncParents($script, $validated, true);
|
|
}
|
|
if ($request->has('assigned_user_ids')) {
|
|
$this->syncAssignees($script, $validated['assigned_user_ids'] ?? [], $request->user());
|
|
}
|
|
});
|
|
|
|
ActivityLogger::log('script_updated', "Script {$script->title} updated", $script);
|
|
|
|
return response()->json($script->fresh(['sections', 'campaign:id,name,sales_script_id', 'product:id,name,sales_script_id', 'assignees:id,name']));
|
|
}
|
|
|
|
public function destroy(SalesScript $script): JsonResponse
|
|
{
|
|
Gate::authorize('delete', $script);
|
|
$title = $script->title;
|
|
$script->delete();
|
|
ActivityLogger::log('script_deleted', "Script {$title} deleted");
|
|
|
|
return response()->json(['message' => 'اسکریپت حذف شد']);
|
|
}
|
|
|
|
private function rules(bool $partial = false): array
|
|
{
|
|
$required = $partial ? 'sometimes' : 'required';
|
|
|
|
return [
|
|
'title' => "{$required}|string|max:255",
|
|
'description' => 'nullable|string',
|
|
'campaign_id' => 'nullable|exists:campaigns,id',
|
|
'product_id' => 'nullable|exists:products,id',
|
|
'version' => 'nullable|string|max:20',
|
|
'is_active' => 'nullable|boolean',
|
|
'checklist' => 'nullable|array',
|
|
'objection_handling' => 'nullable|array',
|
|
'category' => 'nullable|string|max:100',
|
|
'lead_source' => 'nullable|string|max:100',
|
|
'suggested_questions' => 'nullable|array|max:50',
|
|
'required_disclosures' => 'nullable|array|max:50',
|
|
'is_template' => 'nullable|boolean',
|
|
'assigned_user_ids' => 'nullable|array|max:200',
|
|
'assigned_user_ids.*' => 'integer|distinct|exists:users,id',
|
|
'sections' => 'nullable|array',
|
|
'sections.*.id' => 'nullable|integer|exists:script_sections,id',
|
|
'sections.*.title' => 'required|string|max:255',
|
|
'sections.*.content' => 'required|string',
|
|
'sections.*.sort_order' => 'nullable|integer|min:0|distinct',
|
|
];
|
|
}
|
|
|
|
private function authorizeParents(array $validated): void
|
|
{
|
|
if (! empty($validated['campaign_id'])) {
|
|
Gate::authorize('update', Campaign::findOrFail($validated['campaign_id']));
|
|
}
|
|
if (! empty($validated['product_id'])) {
|
|
Gate::authorize('update', Product::findOrFail($validated['product_id']));
|
|
}
|
|
}
|
|
|
|
private function syncParents(SalesScript $script, array $validated, bool $partial = false): void
|
|
{
|
|
if (! $partial || array_key_exists('campaign_id', $validated)) {
|
|
Campaign::where('sales_script_id', $script->id)->update(['sales_script_id' => null]);
|
|
if (! empty($validated['campaign_id'])) {
|
|
Campaign::whereKey($validated['campaign_id'])->update(['sales_script_id' => $script->id]);
|
|
}
|
|
}
|
|
if (! $partial || array_key_exists('product_id', $validated)) {
|
|
Product::where('sales_script_id', $script->id)->update(['sales_script_id' => null]);
|
|
if (! empty($validated['product_id'])) {
|
|
Product::whereKey($validated['product_id'])->update(['sales_script_id' => $script->id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function syncSections(SalesScript $script, array $sections): void
|
|
{
|
|
$existingIds = $script->sections()->pluck('id')->all();
|
|
$keptIds = [];
|
|
DB::table('script_sections')->where('sales_script_id', $script->id)->update(['sort_order' => DB::raw('-id')]);
|
|
foreach (array_values($sections) as $index => $section) {
|
|
$values = ['title' => $section['title'], 'content' => $section['content'], 'sort_order' => $section['sort_order'] ?? $index];
|
|
if (! empty($section['id']) && in_array($section['id'], $existingIds, true)) {
|
|
$script->sections()->whereKey($section['id'])->update($values);
|
|
$keptIds[] = $section['id'];
|
|
} else {
|
|
$keptIds[] = $script->sections()->create($values)->id;
|
|
}
|
|
}
|
|
$script->sections()->whereNotIn('id', $keptIds)->delete();
|
|
}
|
|
|
|
private function authorizeAssignees(array $ids, User $actor): void
|
|
{
|
|
$users = User::whereKey($ids)->where('is_active', true)->get();
|
|
abort_unless($users->count() === count(array_unique($ids)), 422, 'یک یا چند کارشناس انتخابشده معتبر نیستند.');
|
|
foreach ($users as $user) {
|
|
abort_unless($user->hasRole('agent'), 422, 'اسکریپت فقط به کارشناس فروش قابل تخصیص است.');
|
|
abort_unless($actor->hasRole('admin') || AccessControl::canAssignUser($actor, $user->id), 403, 'کارشناس انتخابشده خارج از تیم شما است.');
|
|
}
|
|
}
|
|
|
|
private function syncAssignees(SalesScript $script, array $ids, User $actor): void
|
|
{
|
|
$previous = $script->assignees()->pluck('users.id')->all();
|
|
$script->assignees()->syncWithPivotValues($ids, ['assigned_by' => $actor->id]);
|
|
foreach (array_diff($ids, $previous) as $userId) {
|
|
NotificationService::send((int) $userId, 'اسکریپت فروش جدید', "اسکریپت «{$script->title}» به شما اختصاص داده شد", 'script_assigned', [
|
|
'script_id' => $script->id,
|
|
'url' => '/sales-scripts',
|
|
]);
|
|
}
|
|
}
|
|
}
|