143 خطوط
7.5 KiB
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;
|
|
}
|
|
}
|