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

143 خطوط
7.5 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\AutomationRule;
use App\Models\AutomationRun;
use App\Models\Company;
use App\Models\Contact;
use App\Models\CustomFieldDefinition;
use App\Models\CustomFieldValue;
use App\Models\Lead;
use App\Services\ActivityLogger;
use App\Services\AutomationEngine;
use App\Support\AccessControl;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class ConfigurationController extends Controller
{
private const ENTITIES = ['lead' => Lead::class, 'company' => Company::class, 'contact' => Contact::class];
public function __construct(private AutomationEngine $engine) {}
public function automations(Request $request): JsonResponse
{
abort_unless($request->user()->can('manage_automations') || $request->user()->can('view_automation_logs'), 403);
$query = AutomationRule::withCount('runs')->with('creator:id,name')
->where('trigger', '!=', 'deal_stage_changed')->latest();
if (! $request->user()->hasRole('admin')) {
$query->where(fn ($q) => $q->whereNull('team_id')->orWhereIn('team_id', AccessControl::teamIds($request->user())));
}
return response()->json($query->get());
}
public function storeAutomation(Request $request): JsonResponse
{
abort_unless($request->user()->can('manage_automations'), 403);
$data = $request->validate([
'name' => 'required|string|max:120', 'trigger' => 'required|in:manual,lead_created,lead_scored,sla_breached',
'conditions' => 'nullable|array|max:20', 'actions' => 'required|array|min:1|max:10',
'conditions.*.field' => 'required|string|max:80', 'conditions.*.operator' => 'required|in:equals,not_equals,greater_than,less_than,contains',
'conditions.*.value' => 'nullable',
'actions.*.type' => 'required|in:create_task,notify,set_lead_priority', 'team_id' => 'nullable|exists:teams,id',
'actions.*.subject' => 'nullable|string|max:255', 'actions.*.assigned_to' => 'nullable|exists:users,id',
'actions.*.priority' => 'nullable|in:low,normal,high,urgent', 'actions.*.due_in_minutes' => 'nullable|integer|min:1|max:525600',
'actions.*.user_id' => 'nullable|exists:users,id', 'actions.*.title' => 'nullable|string|max:255',
'actions.*.message' => 'nullable|string|max:1000', 'actions.*.value' => 'nullable|integer|min:0|max:4',
'is_active' => 'boolean', 'max_runs_per_record' => 'integer|min:1|max:20',
]);
if (! $request->user()->hasRole('admin') && ! empty($data['team_id'])) {
abort_unless(in_array((int) $data['team_id'], AccessControl::teamIds($request->user()), true), 403);
}
return response()->json(AutomationRule::create($data + ['created_by' => $request->user()->id]), 201);
}
public function runAutomation(Request $request, AutomationRule $automationRule): JsonResponse
{
abort_unless($request->user()->can('manage_automations'), 403);
$data = $request->validate(['entity_type' => ['required', Rule::in(array_keys(self::ENTITIES))], 'entity_id' => 'required|integer|min:1', 'event_key' => 'nullable|string|max:150']);
$subject = self::ENTITIES[$data['entity_type']]::findOrFail($data['entity_id']);
abort_unless(AccessControl::canAccessEntity($request->user(), $subject), 403);
$key = $data['event_key'] ?? "manual:{$automationRule->id}:{$data['entity_type']}:{$subject->id}:".Str::uuid();
return response()->json($this->engine->run($automationRule, $subject, $key), 202);
}
public function automationRuns(Request $request): JsonResponse
{
abort_unless($request->user()->can('view_automation_logs'), 403);
return response()->json(AutomationRun::with('rule:id,name')->latest()->paginate(min((int) $request->get('per_page', 20), 100)));
}
public function customFields(Request $request): JsonResponse
{
$data = $request->validate(['entity_type' => ['required', Rule::in(array_keys(self::ENTITIES))]]);
$role = $request->user()->roles->first()?->name;
$fields = CustomFieldDefinition::where('entity_type', $data['entity_type'])->where('is_active', true)
->where(fn ($q) => $q->whereNull('visible_to_roles')->orWhereJsonContains('visible_to_roles', $role))->orderBy('sort_order')->get();
return response()->json($fields);
}
public function storeCustomField(Request $request): JsonResponse
{
abort_unless($request->user()->can('manage_custom_fields'), 403);
$data = $request->validate([
'entity_type' => ['required', Rule::in(array_keys(self::ENTITIES))], 'key' => ['required', 'alpha_dash', 'max:80'],
'label' => 'required|string|max:120', 'type' => 'required|in:text,textarea,number,date,datetime,boolean,select,multiselect',
'options' => 'nullable|array|max:100', 'validation' => 'nullable|array|max:20', 'visible_to_roles' => 'nullable|array|max:3',
'default_value' => 'nullable|string|max:1000', 'sort_order' => 'integer|min:0|max:1000', 'is_required' => 'boolean',
'is_active' => 'boolean', 'is_filterable' => 'boolean', 'is_searchable' => 'boolean',
]);
abort_if(CustomFieldDefinition::where('entity_type', $data['entity_type'])->where('key', $data['key'])->exists(), 422, 'کلید فیلد تکراری است.');
return response()->json(CustomFieldDefinition::create($data + ['created_by' => $request->user()->id]), 201);
}
public function values(Request $request, string $entityType, int $entityId): JsonResponse
{
$subject = $this->subject($request, $entityType, $entityId);
return response()->json(CustomFieldValue::with('definition')->where('fieldable_type', $subject::class)->where('fieldable_id', $subject->id)->get());
}
public function updateValues(Request $request, string $entityType, int $entityId): JsonResponse
{
$subject = $this->subject($request, $entityType, $entityId);
$data = $request->validate(['values' => 'required|array|max:100']);
$definitions = CustomFieldDefinition::where('entity_type', $entityType)->where('is_active', true)->get()->keyBy('key');
foreach ($data['values'] as $key => $value) {
$definition = $definitions->get($key);
if (! $definition) {
continue;
}
$column = match ($definition->type) {
'number' => 'value_number', 'date' => 'value_date', 'datetime' => 'value_datetime', 'boolean' => 'value_boolean', 'multiselect' => 'value_json', 'textarea' => 'value_text', default => 'value_string'
};
CustomFieldValue::updateOrCreate([
'custom_field_definition_id' => $definition->id, 'fieldable_type' => $subject::class, 'fieldable_id' => $subject->id,
], [$column => $value, 'updated_by' => $request->user()->id]);
}
ActivityLogger::log('custom_fields_updated', "Custom fields updated for {$entityType} {$entityId}", $subject);
return $this->values($request, $entityType, $entityId);
}
private function subject(Request $request, string $type, int $id): Model
{
abort_unless(isset(self::ENTITIES[$type]), 404);
$subject = self::ENTITIES[$type]::findOrFail($id);
abort_unless(AccessControl::canAccessEntity($request->user(), $subject), 403);
return $subject;
}
}