2026-05-12 20:37:51 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Database\Seeders;
|
|
|
|
|
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use Illuminate\Database\Seeder;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
|
|
|
|
|
|
class DemoSeeder extends Seeder
|
|
|
|
|
{
|
|
|
|
|
public function run(): void
|
|
|
|
|
{
|
2026-05-16 22:20:41 +03:00
|
|
|
// DemoSeeder создаёт демо-данные и НЕ должен исполняться в production.
|
|
|
|
|
// DatabaseSeeder вызывает его только в local/testing — этот guard
|
|
|
|
|
// дополнительно защищает прямой вызов `db:seed --class=DemoSeeder`
|
|
|
|
|
// (в т.ч. через `composer demo:seed`).
|
|
|
|
|
if (app()->isProduction()) {
|
|
|
|
|
$this->command->warn('DemoSeeder пропущен: запрещён в production.');
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 20:37:51 +03:00
|
|
|
$tenant = Tenant::query()->where('subdomain', 'demo')->first()
|
|
|
|
|
?? Tenant::factory()->create([
|
|
|
|
|
'subdomain' => 'demo',
|
|
|
|
|
'organization_name' => 'Demo Tenant',
|
|
|
|
|
'contact_email' => 'admin@demo.local',
|
|
|
|
|
'status' => 'active',
|
|
|
|
|
'balance_rub' => '1000.00',
|
|
|
|
|
'is_trial' => false,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$admin = User::query()->updateOrCreate(
|
|
|
|
|
['email' => 'admin@demo.local'],
|
|
|
|
|
[
|
|
|
|
|
'tenant_id' => $tenant->id,
|
|
|
|
|
'password_hash' => Hash::make('password'),
|
|
|
|
|
'first_name' => 'Demo',
|
|
|
|
|
'last_name' => 'Admin',
|
|
|
|
|
'timezone' => 'Europe/Moscow',
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'totp_enabled' => false,
|
|
|
|
|
'sound_enabled' => true,
|
|
|
|
|
'email_verified_at' => now(),
|
|
|
|
|
'notification_preferences' => [
|
|
|
|
|
'new_lead' => ['inapp' => true, 'push' => true, 'email' => false],
|
|
|
|
|
'reminder' => ['inapp' => true, 'push' => true, 'email' => true],
|
|
|
|
|
'low_balance' => ['email' => true],
|
|
|
|
|
'zero_balance' => ['email' => true],
|
|
|
|
|
'topup_success' => ['email' => true],
|
|
|
|
|
'invoice_paid' => ['email' => true],
|
|
|
|
|
'new_device_login' => ['email' => true],
|
|
|
|
|
'marketing' => ['email' => false],
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->seedProjects($tenant->id);
|
|
|
|
|
$this->seedDeals($tenant->id, $admin->id);
|
|
|
|
|
|
|
|
|
|
$this->command->info("Demo tenant id={$tenant->id} subdomain=demo");
|
|
|
|
|
$this->command->info('Login: admin@demo.local / password');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function seedProjects(int $tenantId): void
|
|
|
|
|
{
|
|
|
|
|
$now = now();
|
|
|
|
|
|
|
|
|
|
$projects = [
|
|
|
|
|
[
|
|
|
|
|
'tag' => 'site',
|
|
|
|
|
'name' => 'Окна СПб (сайт)',
|
|
|
|
|
'type' => 'webhook',
|
|
|
|
|
'signal_type' => 'site',
|
|
|
|
|
'signal_identifier' => 'okna-konkurent.ru',
|
|
|
|
|
'sms_senders' => null,
|
|
|
|
|
'sms_keyword' => null,
|
|
|
|
|
'daily_limit_target' => 50,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'tag' => 'call',
|
|
|
|
|
'name' => 'Натяжные потолки (звонок)',
|
|
|
|
|
'type' => 'webhook',
|
|
|
|
|
'signal_type' => 'call',
|
|
|
|
|
'signal_identifier' => '79161112233',
|
|
|
|
|
'sms_senders' => null,
|
|
|
|
|
'sms_keyword' => null,
|
|
|
|
|
'daily_limit_target' => 30,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'tag' => 'sms',
|
|
|
|
|
'name' => 'Доставка еды (СМС)',
|
|
|
|
|
'type' => 'webhook',
|
|
|
|
|
'signal_type' => 'sms',
|
|
|
|
|
'signal_identifier' => null,
|
|
|
|
|
'sms_senders' => json_encode(['EDA-PROMO', 'YAEDA']),
|
|
|
|
|
'sms_keyword' => 'скидка',
|
|
|
|
|
'daily_limit_target' => 20,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($projects as $p) {
|
|
|
|
|
DB::table('projects')->updateOrInsert(
|
|
|
|
|
['tenant_id' => $tenantId, 'name' => $p['name']],
|
|
|
|
|
[
|
|
|
|
|
'tenant_id' => $tenantId,
|
|
|
|
|
'name' => $p['name'],
|
|
|
|
|
'tag' => $p['tag'],
|
|
|
|
|
'type' => $p['type'],
|
|
|
|
|
'signal_type' => $p['signal_type'],
|
|
|
|
|
'signal_identifier' => $p['signal_identifier'],
|
|
|
|
|
'sms_senders' => $p['sms_senders'],
|
|
|
|
|
'sms_keyword' => $p['sms_keyword'],
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'daily_limit_target' => $p['daily_limit_target'],
|
|
|
|
|
'delivered_today' => 0,
|
|
|
|
|
'delivered_in_month' => 0,
|
|
|
|
|
'region_mask' => 0,
|
|
|
|
|
'region_mode' => 'include',
|
|
|
|
|
'delivery_days_mask' => 127,
|
|
|
|
|
'assignment_strategy' => 'manual',
|
|
|
|
|
'ttfr_target_minutes' => 60,
|
|
|
|
|
'created_at' => $now,
|
|
|
|
|
'updated_at' => $now,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function seedDeals(int $tenantId, int $managerId): void
|
|
|
|
|
{
|
|
|
|
|
$statuses = DB::table('lead_statuses')->orderBy('sort_order')->get();
|
|
|
|
|
$projects = DB::table('projects')
|
|
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->orderBy('id')
|
|
|
|
|
->get()
|
|
|
|
|
->keyBy('signal_type');
|
|
|
|
|
|
|
|
|
|
$samplePool = [
|
|
|
|
|
'site' => [
|
|
|
|
|
['name' => 'Иван Петров', 'phone' => '+79161234501', 'utm' => ['source' => 'yandex', 'medium' => 'cpc', 'campaign' => 'okna-spb']],
|
|
|
|
|
['name' => 'Анна Смирнова', 'phone' => '+79161234502', 'utm' => ['source' => 'google', 'medium' => 'organic', 'campaign' => null]],
|
|
|
|
|
],
|
|
|
|
|
'call' => [
|
|
|
|
|
['name' => 'Сергей Иванов', 'phone' => '+79161234503', 'utm' => ['source' => 'call', 'medium' => 'direct', 'campaign' => null]],
|
|
|
|
|
['name' => 'Мария Кузнецова', 'phone' => '+79161234504', 'utm' => ['source' => 'call', 'medium' => 'direct', 'campaign' => null]],
|
|
|
|
|
],
|
|
|
|
|
'sms' => [
|
|
|
|
|
['name' => 'Дмитрий Соколов', 'phone' => '+79161234505', 'utm' => ['source' => 'sms', 'medium' => 'promo', 'campaign' => 'eda-skidka']],
|
|
|
|
|
['name' => 'Елена Морозова', 'phone' => '+79161234506', 'utm' => ['source' => 'sms', 'medium' => 'promo', 'campaign' => 'eda-skidka']],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$now = now();
|
|
|
|
|
$signalCycle = ['site', 'call', 'sms'];
|
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
|
|
foreach ($statuses as $status) {
|
|
|
|
|
$signal = $signalCycle[$i % 3];
|
|
|
|
|
$sample = $samplePool[$signal][$i % 2];
|
|
|
|
|
$project = $projects[$signal];
|
|
|
|
|
|
|
|
|
|
$existing = DB::table('deals')
|
|
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->where('phone', $sample['phone'])
|
|
|
|
|
->where('status', $status->slug)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($existing) {
|
|
|
|
|
$i++;
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DB::table('deals')->insert([
|
|
|
|
|
'tenant_id' => $tenantId,
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'phone' => $sample['phone'],
|
|
|
|
|
'phones' => json_encode([$sample['phone']]),
|
|
|
|
|
'status' => $status->slug,
|
|
|
|
|
'contact_name' => $sample['name'],
|
|
|
|
|
'comment' => "Демо-сделка статуса «{$status->name_ru}» ({$signal})",
|
|
|
|
|
'manager_id' => $managerId,
|
|
|
|
|
'assigned_at' => $now,
|
|
|
|
|
'escalated_count' => 0,
|
|
|
|
|
'utm_source' => $sample['utm']['source'],
|
|
|
|
|
'utm_medium' => $sample['utm']['medium'],
|
|
|
|
|
'utm_campaign' => $sample['utm']['campaign'],
|
|
|
|
|
'region_code' => $i % 2 === 0 ? '77' : '78',
|
|
|
|
|
'city' => $i % 2 === 0 ? 'Москва' : 'Санкт-Петербург',
|
|
|
|
|
'time_in_form_seconds' => 30 + $i * 5,
|
|
|
|
|
'lead_score' => number_format(50.0 + $i * 3, 2, '.', ''),
|
|
|
|
|
'is_test' => false,
|
|
|
|
|
'received_at' => $now->copy()->subMinutes($i * 7),
|
|
|
|
|
'created_at' => $now,
|
|
|
|
|
'updated_at' => $now,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|