', 'قالب Caller ID', 'عبارت {extension} با شماره داخلی کاربر جایگزین می‌شود.', true, false, false, null, ['AmiProvider']), self::d('voip_ami_timeout', 'voip', 'integer', '5', 'مهلت اتصال AMI', 'حداکثر زمان انتظار برای اتصال socket به AMI.', true, false, false, null, ['AmiProvider']), self::d('voip_ami_originate_timeout', 'voip', 'integer', '30', 'مهلت زنگ خوردن داخلی', 'مهلت Originate بر حسب ثانیه.', true, false, false, null, ['AmiProvider']), self::d('voip_ami_recording_url', 'voip', 'string', '', 'قالب لینک ضبط AMI', 'اگر لینک ضبط از الگو ساخته می‌شود، {id} با شناسه تماس جایگزین می‌شود.', true, false, false, null, ['AmiProvider']), self::d('voip_api_base_url', 'voip', 'string', '', 'Base URL', 'در provider API استفاده می‌شود.', true, false, false, null, ['ApiProvider']), self::d('voip_api_token', 'voip', 'string', '', 'Token', 'به صورت secret نگهداری و در API/Socket استفاده می‌شود.', true, false, true, null, ['ApiProvider']), self::d('voip_api_call_path', 'voip', 'string', '/calls', 'مسیر API تماس', 'در provider API استفاده می‌شود.', true, false, false, null, ['ApiProvider']), self::d('voip_api_status_path', 'voip', 'string', '/calls/{id}', 'Status endpoint', 'در provider API استفاده می‌شود.', true, false, false, null, ['ApiProvider']), self::d('voip_api_health_path', 'voip', 'string', '/health', 'Health endpoint', 'برای تست واقعی اتصال احراز هویت‌شده به سرویس تماس استفاده می‌شود.', true, false, false, null, ['ApiProvider']), self::d('voip_api_recording_url', 'voip', 'string', '', 'Recording endpoint', 'برای ساخت لینک ضبط استفاده می‌شود.', true, false, false, null, ['ApiProvider']), self::d('voip_webhook_path', 'voip', 'string', '/api/voip/webhook', 'Webhook endpoint', 'این مسیر رویدادهای امضاشده وضعیت، مدت و ضبط تماس را دریافت می‌کند.', true, false, false, null, ['VoipWebhookController']), self::d('voip_webhook_secret', 'voip', 'string', '', 'رمز امضای Webhook', 'برای اعتبارسنجی HMAC رویدادهای وضعیت، مدت و ضبط تماس استفاده می‌شود.', true, false, true, null, ['VoipWebhookController']), self::d('voip_socket_timeout', 'voip', 'integer', '5', 'Timeout', 'در اتصال socket استفاده می‌شود.', true, false, false, null, ['SocketProvider']), self::d('call_recording_enabled', 'voip', 'boolean', 'false', 'ضبط تماس', 'در ساخت تماس و نمایش recording اعمال می‌شود.', true, false, false, null, ['CallService']), self::d('recording_retention_days', 'voip', 'integer', '90', 'نگهداری ضبط', 'پاکسازی خودکار ضبط‌ها به‌زودی.', false, false, false, null, [], true), self::d('minimum_call_duration_seconds', 'voip', 'integer', '10', 'حداقل مدت تماس', 'اعتبارسنجی duration به‌زودی.', false, false, false, null, [], true), self::d('call_result_required', 'voip', 'boolean', 'true', 'ثبت نتیجه تماس الزامی', 'فرم‌های ثبت نتیجه در UI/API الزام نتیجه دارند.', true, false, false, null, ['CallController']), self::d('default_follow_up_hours', 'follow_up', 'integer', '24', 'زمان پیش‌فرض پیگیری', 'برای ایجاد پیگیری پیش‌فرض استفاده می‌شود.', true, false, false, null, ['CallService']), self::d('reminder_before_due_minutes', 'follow_up', 'integer', '30', 'یادآوری قبل از موعد', 'نوتیفیکیشن زمان‌بندی‌شده به‌زودی.', false, false, false, null, [], true), self::d('overdue_alert_hours', 'follow_up', 'integer', '2', 'هشدار تأخیر', 'در گزارش عقب‌افتادگی نگهداری می‌شود.', false, false, false, null, [], true), self::d('escalate_overdue_to_supervisor', 'follow_up', 'boolean', 'false', 'ارجاع تأخیر به مدیر فروش', 'به‌زودی.', false, false, false, null, [], true), self::d('auto_follow_up_call_results', 'follow_up', 'json', 'بعداً تماس بگیرید,نیازمند پیگیری,معرفی شماره جدید', 'نتایج تماس نیازمند پیگیری', 'با requires_follow_up در call_results همگام می‌شود.', true, false, false, null, ['CallController']), self::d('prevent_follow_up_outside_working_hours', 'follow_up', 'boolean', 'false', 'جلوگیری خارج از ساعت کاری', 'در ساخت پیگیری دستی و بعد از تماس اعمال می‌شود.', true, false, false, null, ['FollowUpController', 'CallService']), self::d('in_app_notifications_enabled', 'notification', 'boolean', 'true', 'اعلان داخل برنامه', 'سیستم اعلان داخلی فعال است.', true, false, false, null, ['NotificationService']), self::d('browser_notifications_enabled', 'notification', 'boolean', 'false', 'اعلان مرورگر/PWA', 'اجازه push/browser به‌زودی.', false, false, false, null, [], true), self::d('new_lead_notification_enabled', 'notification', 'boolean', 'true', 'اعلان لید جدید', 'در تخصیص‌ها و آینده ایجاد لید استفاده می‌شود.', true, false, false, null, ['NotificationService']), self::d('assigned_lead_notification_enabled', 'notification', 'boolean', 'true', 'اعلان تخصیص لید', 'در LeadService و ImportService استفاده می‌شود.', true, false, false, null, ['LeadService', 'ImportService']), self::d('follow_up_notification_enabled', 'notification', 'boolean', 'true', 'اعلان پیگیری', 'در FollowUpController و CallService استفاده می‌شود.', true, false, false, null, ['FollowUpController', 'CallService']), self::d('overdue_notification_enabled', 'notification', 'boolean', 'true', 'اعلان پیگیری عقب‌افتاده', 'در endpoint پیگیری‌های عقب‌افتاده استفاده می‌شود.', true, false, false, null, ['FollowUpController@overdue']), self::d('quiet_hours_start', 'notification', 'string', '22:00', 'شروع سکوت اعلان', 'اعمال quiet hours به‌زودی.', false, false, false, null, [], true), self::d('quiet_hours_end', 'notification', 'string', '08:00', 'پایان سکوت اعلان', 'اعمال quiet hours به‌زودی.', false, false, false, null, [], true), self::d('import_required_fields', 'import_export', 'json', 'company,phone', 'فیلدهای الزامی ورود داده', 'در ورود فعلی company و phone الزامی هستند.', true, false, false, null, ['ImportController']), self::d('import_max_file_size_mb', 'import_export', 'integer', '10', 'حداکثر حجم ورود داده', 'در بارگذاری فایل ورود داده اعمال می‌شود.', true, false, false, null, ['ImportController']), self::d('import_allowed_file_types', 'import_export', 'json', 'xlsx,xls,csv', 'نوع فایل ورود داده', 'در بارگذاری فایل ورود داده اعمال می‌شود.', true, false, false, null, ['ImportController']), self::d('export_phone_mode', 'import_export', 'string', 'permission_based', 'حالت شماره در خروجی', 'full فقط با view_full_phone؛ masked برای بقیه.', true, false, false, ['permission_based', 'masked_only'], ['LeadController@export']), self::d('import_rollback_allowed', 'import_export', 'boolean', 'true', 'بازگردانی ورود داده', 'در ساخت دسته ورود و بازگردانی ورود داده استفاده می‌شود.', true, false, false, null, ['ImportController']), self::d('mapping_presets', 'import_export', 'json', '', 'الگوهای نگاشت', 'الگوهای نگاشت به‌زودی.', false, false, false, null, [], true), self::d('attachment_max_file_size_mb', 'import_export', 'integer', '10', 'حداکثر حجم فایل پیوست', 'در بارگذاری فایل‌های لید/شرکت/فرصت اعمال می‌شود.', true, false, false, null, ['AttachmentController']), self::d('attachment_allowed_file_types', 'import_export', 'json', 'pdf,jpg,jpeg,png,doc,docx,xls,xlsx,txt', 'نوع فایل پیوست', 'در بارگذاری فایل‌های پیوست اعمال می‌شود.', true, false, false, null, ['AttachmentController']), self::d('daily_call_target', 'reports', 'integer', '40', 'هدف تماس روزانه', 'در داشبورد کارشناس فروش و هشدارهای مدیر فروش/مدیریت استفاده می‌شود.', true, false, false, null, ['DashboardService']), self::d('successful_call_target', 'reports', 'integer', '15', 'هدف تماس موفق', 'در پیشرفت KPI کارشناس استفاده می‌شود.', true, false, false, null, ['DashboardService']), self::d('follow_up_target', 'reports', 'integer', '20', 'هدف پیگیری', 'در پیشرفت KPI کارشناس استفاده می‌شود.', true, false, false, null, ['DashboardService']), self::d('conversion_target_percent', 'reports', 'integer', '20', 'هدف تبدیل', 'برای مقایسه مدیریتی نرخ تبدیل نگهداری و در گزارش‌ها قابل استفاده است.', true, false, false, null, ['ReportService', 'DashboardService']), self::d('default_report_range', 'reports', 'string', 'month', 'بازه پیش‌فرض گزارش', 'اعمال پیش‌فرض گزارش به‌زودی.', false, false, false, ['today', 'week', 'month', 'quarter'], [], true), self::d('pwa_enabled', 'pwa', 'boolean', 'true', 'فعال بودن PWA', 'manifest/service worker موجود است؛ خاموش کردن runtime به‌زودی.', false, true, false, null, [], true), self::d('pwa_install_banner_text', 'pwa', 'string', 'نصب CRM فروش', 'متن بنر نصب PWA', 'در بنر نصب PWA نمایش داده می‌شود.', false, true, false, null, [], true), self::d('pwa_theme_color', 'pwa', 'string', '#2563eb', 'رنگ theme', 'در پاسخ عمومی منتشر می‌شود؛ تزریق manifest به‌زودی.', false, true, false, null, [], true), self::d('pwa_status_bar_color', 'pwa', 'string', '#ffffff', 'رنگ نوار وضعیت', 'اعمال کامل به‌زودی.', false, true, false, null, [], true), self::d('ui_density', 'pwa', 'string', 'comfortable', 'حالت نمایش', 'compact/comfortable در UI آینده اعمال می‌شود.', false, true, false, ['compact', 'comfortable'], [], true), self::d('default_landing_page', 'pwa', 'string', '/', 'صفحه پیش‌فرض', 'redirect پویا به‌زودی.', false, true, false, ['/', '/leads', '/crm', '/agent-mobile/today'], [], true), self::d('offline_cache_duration_hours', 'pwa', 'integer', '24', 'مدت cache آفلاین', 'service worker پویا به‌زودی.', false, false, false, null, [], true), self::d('push_permission_text', 'pwa', 'string', 'برای دریافت یادآوری‌ها اعلان‌ها را فعال کنید.', 'متن اجازه push', 'push notification به‌زودی.', false, true, false, null, [], true), ]; } public static function ensureDefaults(): void { foreach (self::definitions() as $definition) { $setting = Setting::firstOrNew(['key' => $definition['key']]); $setting->fill([ 'value' => $setting->exists ? $setting->value : $definition['default_value'], 'default_value' => $definition['default_value'], 'group' => $definition['group'], 'type' => $definition['type'], 'allowed_values' => $definition['allowed_values'], 'is_secret' => $definition['is_secret'], 'is_public' => $definition['is_public'], 'is_runtime_enforced' => $definition['is_runtime_enforced'], 'description' => $definition['hint'], 'validation_rules' => self::rulesFor($definition), ])->save(); } } public static function metaFor(string $key): ?array { foreach (self::definitions() as $definition) { if ($definition['key'] === $key) { return $definition; } } return null; } private static function d(string $key, string $group, string $type, string $default, string $label, string $hint, bool $enforced, bool $public = false, bool $secret = false, ?array $allowed = null, array $usedBy = [], bool $comingSoon = false): array { return [ 'key' => $key, 'group' => $group, 'type' => $type, 'default_value' => $default, 'label' => $label, 'hint' => $hint, 'allowed_values' => $allowed, 'is_runtime_enforced' => $enforced, 'is_public' => $public, 'is_secret' => $secret, 'used_by' => $usedBy, 'coming_soon' => $comingSoon, ]; } private static function rulesFor(array $definition): array { return match ($definition['type']) { 'boolean' => ['in:true,false,1,0'], 'integer' => ['integer'], 'json' => ['nullable', 'string'], default => ['nullable', 'string'], }; } }