user() !== null; } public function rules(): array { // signal_type immutable: не валидируется в правилах, controller игнорирует поле $rules = [ 'name' => ['sometimes', 'string', 'max:255'], 'daily_limit_target' => ['sometimes', 'integer', 'min:1', 'max:10000'], // Plan 6: subject-level regions[] заменил region_mask/region_mode на API-уровне. // sometimes = поле omit-able (preserves prior DB value), массив + each 1..89. 'regions' => ['sometimes', 'array'], 'regions.*' => ['integer', 'between:1,89'], 'delivery_days_mask' => ['sometimes', 'integer', 'min:1', 'max:127'], 'sms_senders' => ['sometimes', 'array', 'min:1'], 'sms_senders.*' => ['string', 'max:11'], 'sms_keyword' => ['sometimes', 'nullable', 'string', 'min:1', 'max:50'], // Spec C §3.4: при перегрузке преfflight UI шлёт force_save_blocked=true → // проект помечается preflight_blocked_at=now() вместо ответа 409. 'force_save_blocked' => ['sometimes', 'boolean'], ]; // 18.05.2026 UX: редактирование источника (signal_identifier) для site/call. // Регулярки соответствуют StoreProjectRequest (domain + 7\d{10}). // signal_type immutable — берём из текущего проекта по route id. $projectId = $this->route('id'); if ($projectId !== null) { $project = Project::find($projectId); if ($project !== null) { if ($project->signal_type === 'site') { $rules['signal_identifier'] = ['sometimes', 'string', 'regex:/^[a-z0-9][a-z0-9\-]*(\.[a-z0-9][a-z0-9\-]*)*\.[a-z]{2,}$/i']; } elseif ($project->signal_type === 'call') { $rules['signal_identifier'] = ['sometimes', 'string', 'regex:/^7\d{10}$/']; } // sms: signal_identifier меняется через sms_senders/sms_keyword (см. выше) } } return $rules; } }