diff --git a/website/database/seeders/DatabaseSeeder.php b/website/database/seeders/DatabaseSeeder.php index 7e828dd..b539466 100644 --- a/website/database/seeders/DatabaseSeeder.php +++ b/website/database/seeders/DatabaseSeeder.php @@ -14,6 +14,7 @@ class DatabaseSeeder extends Seeder RoleAndPermissionSeeder::class, PlanSeeder::class, AdminUserSeeder::class, + DemoDataSeeder::class, ]); } } diff --git a/website/database/seeders/DemoDataSeeder.php b/website/database/seeders/DemoDataSeeder.php new file mode 100644 index 0000000..d0cbf2f --- /dev/null +++ b/website/database/seeders/DemoDataSeeder.php @@ -0,0 +1,864 @@ +where('email', 'like', '%@'.self::DEMO_DOMAIN)->exists()) { + $this->command->info('Demo data already exists. Skipping DemoDataSeeder.'); + + return; + } + + $this->command->info('Seeding demo data...'); + + // Load real plans from PlanSeeder + $plans = Plan::query()->where('status', 'active')->get(); + + if ($plans->isEmpty()) { + $this->command->error('No plans found. Run PlanSeeder first.'); + + return; + } + + // Grab admin user for audit logs / staff replies + $adminUser = User::role('admin')->first(); + $adminId = $adminUser?->id ?? 1; + + // ─── 1. Create ~300 Customers ───────────────────────────────── + $this->command->info('Creating customers...'); + $customers = $this->createCustomers(); + + // ─── 2. Create ~500 Subscriptions ───────────────────────────── + $this->command->info('Creating subscriptions...'); + $subscriptionMap = $this->createSubscriptions($customers, $plans); + + // ─── 3. Create ~400 Services ────────────────────────────────── + $this->command->info('Creating services...'); + $this->createServices($customers, $plans, $subscriptionMap); + + // ─── 4. Create ~800 Invoices with Items ─────────────────────── + $this->command->info('Creating invoices...'); + $invoiceIds = $this->createInvoices($customers, $plans, $subscriptionMap); + + // ─── 5. Create ~600 Payment Transactions ────────────────────── + $this->command->info('Creating payment transactions...'); + $this->createPaymentTransactions($customers, $invoiceIds); + + // ─── 6. Create ~150 Orders ──────────────────────────────────── + $this->command->info('Creating orders...'); + $this->createOrders($customers, $plans); + + // ─── 7. Create ~200 Support Tickets with Replies ────────────── + $this->command->info('Creating support tickets...'); + $this->createSupportTickets($customers, $adminId); + + // ─── 8. Create ~50 Coupons ──────────────────────────────────── + $this->command->info('Creating coupons...'); + $this->createCoupons(); + + // ─── 9. Create ~100 Audit Logs ──────────────────────────────── + $this->command->info('Creating audit logs...'); + $this->createAuditLogs($customers, $adminId); + + $this->command->info('Demo data seeded successfully!'); + } + + /** + * Create ~300 customers with profiles. + * + * @return \Illuminate\Support\Collection + */ + private function createCustomers(): \Illuminate\Support\Collection + { + $customers = collect(); + $statuses = array_merge( + array_fill(0, 270, 'active'), + array_fill(0, 15, 'suspended'), + array_fill(0, 10, 'banned'), + array_fill(0, 5, 'pending'), + ); + shuffle($statuses); + + $faker = fake(); + $batchSize = 50; + $totalCustomers = 300; + + for ($i = 0; $i < $totalCustomers; $i += $batchSize) { + $batchCount = min($batchSize, $totalCustomers - $i); + + for ($j = 0; $j < $batchCount; $j++) { + $index = $i + $j; + $createdAt = now()->subDays(rand(1, 365)); + + $user = User::factory()->create([ + 'name' => $faker->name(), + 'email' => "demo_{$index}@".self::DEMO_DOMAIN, + 'status' => $statuses[$index] ?? 'active', + 'phone' => $faker->optional(0.7)->phoneNumber(), + 'company' => $faker->optional(0.4)->company(), + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]); + + $user->assignRole('customer'); + + $user->profile()->create([ + 'billing_address_line1' => $faker->streetAddress(), + 'billing_city' => $faker->city(), + 'billing_state' => $faker->stateAbbr(), + 'billing_zip' => $faker->postcode(), + 'billing_country' => $faker->randomElement(['US', 'CA', 'GB', 'DE', 'FR', 'AU', 'NL', 'SE']), + 'tax_exempt' => $faker->boolean(5), + 'company_name' => $user->company, + ]); + + $customers->push($user); + } + } + + return $customers; + } + + /** + * Create ~500 subscriptions linked to real plans. + * + * Returns a map of subscription_id => [user_id, plan_id] for use by other seeders. + * + * @param \Illuminate\Support\Collection $customers + * @param \Illuminate\Support\Collection $plans + * @return array + */ + private function createSubscriptions(\Illuminate\Support\Collection $customers, \Illuminate\Support\Collection $plans): array + { + $subscriptionMap = []; + $statuses = ['active', 'active', 'active', 'active', 'active', 'active', 'active', 'canceled', 'past_due', 'trialing']; + $total = 500; + $rows = []; + + for ($i = 0; $i < $total; $i++) { + $customer = $customers->random(); + $plan = $plans->random(); + $status = $statuses[array_rand($statuses)]; + $createdAt = $customer->created_at->copy()->addDays(rand(0, 60)); + + if ($createdAt->isFuture()) { + $createdAt = now()->subDays(rand(1, 30)); + } + + $periodStart = $createdAt->copy(); + $periodEnd = match ($plan->billing_cycle) { + 'quarterly' => $periodStart->copy()->addMonths(3), + 'annual' => $periodStart->copy()->addYear(), + default => $periodStart->copy()->addMonth(), + }; + + $endsAt = null; + $cancelledAt = null; + if ($status === 'canceled') { + $cancelledAt = $createdAt->copy()->addDays(rand(30, 180)); + $endsAt = $cancelledAt->copy()->addDays(rand(0, 30)); + } + + $trialEndsAt = null; + if ($status === 'trialing') { + $trialEndsAt = now()->addDays(rand(1, 14)); + } + + $rows[] = [ + 'user_id' => $customer->id, + 'type' => 'default', + 'stripe_id' => 'sub_demo_'.Str::random(14).'_'.$i, + 'stripe_status' => $status, + 'stripe_price' => 'price_demo_'.Str::random(10), + 'quantity' => 1, + 'trial_ends_at' => $trialEndsAt, + 'ends_at' => $endsAt, + 'plan_id' => $plan->id, + 'gateway' => fake()->randomElement(['stripe', 'stripe', 'stripe', 'paypal']), + 'gateway_subscription_id' => 'sub_demo_'.Str::random(14), + 'gateway_customer_id' => 'cus_demo_'.Str::random(14), + 'gateway_price_id' => 'price_demo_'.Str::random(14), + 'current_period_start' => $periodStart, + 'current_period_end' => $periodEnd, + 'cancelled_at' => $cancelledAt, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + // Bulk insert in chunks + foreach (array_chunk($rows, 100) as $chunk) { + DB::table('subscriptions')->insert($chunk); + } + + // Build the subscription map (query back IDs) + $allSubs = DB::table('subscriptions') + ->where('stripe_id', 'like', 'sub_demo_%') + ->select('id', 'user_id', 'plan_id') + ->get(); + + foreach ($allSubs as $sub) { + $subscriptionMap[$sub->id] = [ + 'user_id' => $sub->user_id, + 'plan_id' => $sub->plan_id, + ]; + } + + return $subscriptionMap; + } + + /** + * Create ~400 services. + * + * @param \Illuminate\Support\Collection $customers + * @param \Illuminate\Support\Collection $plans + * @param array $subscriptionMap + */ + private function createServices(\Illuminate\Support\Collection $customers, \Illuminate\Support\Collection $plans, array $subscriptionMap): void + { + $platformMap = [ + 'vps' => 'virtfusion', + 'dedicated' => 'synergycp', + 'hosting' => 'enhance', + 'game_server' => 'pterodactyl', + 'mysql' => 'enhance', + ]; + + $serviceStatuses = array_merge( + array_fill(0, 300, 'active'), + array_fill(0, 40, 'suspended'), + array_fill(0, 30, 'pending'), + array_fill(0, 30, 'terminated'), + ); + shuffle($serviceStatuses); + + $subIds = array_keys($subscriptionMap); + $rows = []; + $faker = fake(); + $total = 400; + + for ($i = 0; $i < $total; $i++) { + $customer = $customers->random(); + $plan = $plans->random(); + $status = $serviceStatuses[$i] ?? 'active'; + $serviceType = $plan->service_type; + $platform = $platformMap[$serviceType] ?? 'virtfusion'; + + // Try to link to a subscription for this user + $subId = null; + foreach ($subIds as $sid) { + if ($subscriptionMap[$sid]['user_id'] === $customer->id) { + $subId = $sid; + break; + } + } + + $createdAt = $customer->created_at->copy()->addDays(rand(1, 90)); + if ($createdAt->isFuture()) { + $createdAt = now()->subDays(rand(1, 30)); + } + + $provisionedAt = $status !== 'pending' ? $createdAt->copy()->addHours(rand(1, 48)) : null; + $suspendedAt = $status === 'suspended' ? $createdAt->copy()->addDays(rand(30, 120)) : null; + $terminatedAt = $status === 'terminated' ? $createdAt->copy()->addDays(rand(60, 180)) : null; + + $hostParts = [$faker->domainWord(), $faker->randomElement(['us-east', 'us-west', 'eu-west', 'ap-south'])]; + + $rows[] = [ + 'user_id' => $customer->id, + 'subscription_id' => $subId, + 'plan_id' => $plan->id, + 'service_type' => $serviceType, + 'platform' => $platform, + 'platform_service_id' => (string) $faker->numberBetween(10000, 99999), + 'status' => $status, + 'ipv4_address' => $faker->ipv4(), + 'ipv6_address' => $faker->optional(0.3)->ipv6(), + 'hostname' => implode('-', $hostParts).'.ezscale.cloud', + 'domain' => $faker->optional(0.5)->domainName(), + 'credentials' => null, + 'provisioned_at' => $provisionedAt, + 'suspended_at' => $suspendedAt, + 'terminated_at' => $terminatedAt, + 'auto_renew' => $faker->boolean(85), + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + foreach (array_chunk($rows, 100) as $chunk) { + DB::table('services')->insert($chunk); + } + } + + /** + * Create ~800 invoices with line items. + * + * @param \Illuminate\Support\Collection $customers + * @param \Illuminate\Support\Collection $plans + * @param array $subscriptionMap + * @return array + */ + private function createInvoices(\Illuminate\Support\Collection $customers, \Illuminate\Support\Collection $plans, array $subscriptionMap): array + { + $invoiceStatuses = array_merge( + array_fill(0, 500, 'paid'), + array_fill(0, 150, 'pending'), + array_fill(0, 100, 'overdue'), + array_fill(0, 50, 'void'), + ); + shuffle($invoiceStatuses); + + $subIds = array_keys($subscriptionMap); + $invoiceRows = []; + $invoiceTracker = []; + $faker = fake(); + $total = 800; + + for ($i = 0; $i < $total; $i++) { + $customer = $customers->random(); + $plan = $plans->random(); + $status = $invoiceStatuses[$i] ?? 'paid'; + + // Link to a subscription if possible + $subId = null; + foreach ($subIds as $sid) { + if ($subscriptionMap[$sid]['user_id'] === $customer->id) { + $subId = $sid; + break; + } + } + + $price = (float) $plan->price; + $tax = round($price * fake()->randomFloat(2, 0.05, 0.12), 2); + $invoiceTotal = round($price + $tax, 2); + + $createdAt = now()->subDays(rand(1, 365)); + $dueDate = $createdAt->copy()->addDays(rand(14, 30)); + $paidAt = null; + + if ($status === 'paid') { + $paidAt = $createdAt->copy()->addDays(rand(0, 14)); + } + + if ($status === 'overdue') { + $dueDate = now()->subDays(rand(1, 60)); + } + + $gateway = $faker->randomElement(['stripe', 'stripe', 'stripe', 'paypal']); + + $invoiceRows[] = [ + 'user_id' => $customer->id, + 'subscription_id' => $subId, + 'gateway' => $gateway, + 'gateway_invoice_id' => 'in_demo_'.Str::random(14).'_'.$i, + 'number' => 'INV-'.str_pad((string) (100000 + $i), 6, '0', STR_PAD_LEFT), + 'total' => $invoiceTotal, + 'tax' => $tax, + 'currency' => 'USD', + 'status' => $status, + 'invoice_pdf' => null, + 'due_date' => $dueDate, + 'paid_at' => $paidAt, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + + $invoiceTracker[] = [ + 'user_id' => $customer->id, + 'total' => $invoiceTotal, + 'status' => $status, + 'plan_name' => $plan->name, + 'plan_price' => $price, + ]; + } + + foreach (array_chunk($invoiceRows, 100) as $chunk) { + DB::table('invoices')->insert($chunk); + } + + // Get the IDs back for invoice items and payment transactions + $invoiceIds = DB::table('invoices') + ->where('gateway_invoice_id', 'like', 'in_demo_%') + ->orderBy('id') + ->pluck('id') + ->toArray(); + + // Create invoice items for each invoice + $itemRows = []; + foreach ($invoiceIds as $idx => $invoiceId) { + $tracker = $invoiceTracker[$idx] ?? null; + if (! $tracker) { + continue; + } + + $itemRows[] = [ + 'invoice_id' => $invoiceId, + 'description' => ($tracker['plan_name'] ?? 'Service').' - Monthly Subscription', + 'amount' => $tracker['plan_price'] ?? 10.00, + 'quantity' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]; + } + + foreach (array_chunk($itemRows, 200) as $chunk) { + DB::table('invoice_items')->insert($chunk); + } + + // Build return data + $result = []; + foreach ($invoiceIds as $idx => $invoiceId) { + $tracker = $invoiceTracker[$idx] ?? null; + $result[] = [ + 'id' => $invoiceId, + 'user_id' => $tracker['user_id'] ?? 0, + 'total' => $tracker['total'] ?? 0, + 'status' => $tracker['status'] ?? 'paid', + ]; + } + + return $result; + } + + /** + * Create ~600 payment transactions. + * + * @param \Illuminate\Support\Collection $customers + * @param array $invoiceData + */ + private function createPaymentTransactions(\Illuminate\Support\Collection $customers, array $invoiceData): void + { + $rows = []; + $faker = fake(); + $total = 600; + + // Use paid/pending invoices for payment transactions + $paidInvoices = array_filter($invoiceData, fn ($inv) => $inv['status'] === 'paid'); + $paidInvoices = array_values($paidInvoices); + + $transactionStatuses = array_merge( + array_fill(0, 480, 'succeeded'), + array_fill(0, 60, 'failed'), + array_fill(0, 40, 'refunded'), + array_fill(0, 20, 'pending'), + ); + shuffle($transactionStatuses); + + for ($i = 0; $i < $total; $i++) { + $status = $transactionStatuses[$i] ?? 'succeeded'; + + // Link to an invoice if available + $invoiceId = null; + $userId = $customers->random()->id; + $amount = round($faker->randomFloat(2, 4, 200), 2); + + if (! empty($paidInvoices) && $i < count($paidInvoices)) { + $inv = $paidInvoices[$i]; + $invoiceId = $inv['id']; + $userId = $inv['user_id']; + $amount = (float) $inv['total']; + } + + $gateway = $faker->randomElement(['stripe', 'stripe', 'stripe', 'paypal']); + $paymentMethod = $gateway === 'stripe' + ? $faker->randomElement(['card', 'card', 'card', 'bank_transfer']) + : 'paypal'; + + $createdAt = now()->subDays(rand(1, 365)); + + $rows[] = [ + 'user_id' => $userId, + 'subscription_id' => null, + 'invoice_id' => $invoiceId, + 'gateway' => $gateway, + 'gateway_transaction_id' => ($gateway === 'stripe' ? 'pi_demo_' : 'PAY-DEMO-').Str::random(14).'_'.$i, + 'amount' => $amount, + 'currency' => 'USD', + 'status' => $status, + 'payment_method' => $paymentMethod, + 'description' => match ($status) { + 'succeeded' => 'Payment for hosting service', + 'failed' => 'Payment declined - '.fake()->randomElement(['insufficient funds', 'card expired', 'bank declined']), + 'refunded' => 'Refund processed', + 'pending' => 'Payment processing', + default => 'Payment', + }, + 'metadata' => json_encode(['demo' => true]), + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + foreach (array_chunk($rows, 100) as $chunk) { + DB::table('payment_transactions')->insert($chunk); + } + } + + /** + * Create ~150 orders. + * + * @param \Illuminate\Support\Collection $customers + * @param \Illuminate\Support\Collection $plans + */ + private function createOrders(\Illuminate\Support\Collection $customers, \Illuminate\Support\Collection $plans): void + { + $orderStatuses = array_merge( + array_fill(0, 80, 'completed'), + array_fill(0, 30, 'pending'), + array_fill(0, 25, 'processing'), + array_fill(0, 15, 'cancelled'), + ); + shuffle($orderStatuses); + + $rows = []; + $faker = fake(); + $total = 150; + + for ($i = 0; $i < $total; $i++) { + $customer = $customers->random(); + $plan = $plans->random(); + $status = $orderStatuses[$i] ?? 'completed'; + $createdAt = now()->subDays(rand(1, 365)); + + $completedAt = null; + $cancelledAt = null; + + if ($status === 'completed') { + $completedAt = $createdAt->copy()->addDays(rand(0, 3)); + } + + if ($status === 'cancelled') { + $cancelledAt = $createdAt->copy()->addDays(rand(0, 7)); + } + + $rows[] = [ + 'user_id' => $customer->id, + 'plan_id' => $plan->id, + 'invoice_id' => null, + 'service_id' => null, + 'order_number' => 'ORD-'.strtoupper(Str::random(8)).$i, + 'status' => $status, + 'total' => (float) $plan->price, + 'currency' => 'USD', + 'payment_gateway' => $faker->randomElement(['stripe', 'paypal']), + 'configuration' => json_encode([ + 'hostname' => $faker->domainWord().'.ezscale.cloud', + 'location' => $faker->randomElement(['us-east-1', 'us-west-1', 'eu-west-1', 'ap-south-1']), + 'os' => $faker->randomElement(['Ubuntu 24.04', 'Debian 12', 'Rocky Linux 9', 'AlmaLinux 9', 'Windows Server 2022']), + ]), + 'admin_notes' => $faker->optional(0.2)->sentence(), + 'completed_at' => $completedAt, + 'cancelled_at' => $cancelledAt, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + foreach (array_chunk($rows, 100) as $chunk) { + DB::table('orders')->insert($chunk); + } + } + + /** + * Create ~200 support tickets with 2-5 replies each. + * + * @param \Illuminate\Support\Collection $customers + */ + private function createSupportTickets(\Illuminate\Support\Collection $customers, int $adminId): void + { + $faker = fake(); + $ticketSubjects = [ + 'billing' => [ + 'Invoice not received', 'Payment failed', 'Request refund', 'Billing cycle question', + 'Upgrade pricing inquiry', 'Coupon not working', 'Double charged', 'Payment method update help', + 'Tax exemption request', 'Downgrade subscription', + ], + 'technical' => [ + 'Server not responding', 'High CPU usage', 'Cannot SSH into server', 'DNS not resolving', + 'SSL certificate issue', 'Database connection error', 'Slow website performance', + 'Firewall blocking port', 'Disk space full', 'Container restart loop', + ], + 'sales' => [ + 'Custom server configuration', 'Volume discount inquiry', 'Enterprise plan availability', + 'Reseller program info', 'Data center locations', 'SLA details', 'Migration assistance', + 'Bulk order pricing', 'Partner program', 'Referral program', + ], + 'general' => [ + 'Account access issue', 'Two-factor auth locked out', 'Email not verified', + 'Profile update help', 'API documentation', 'Feature request', 'Service status inquiry', + 'Password reset not working', 'Terms of service question', 'Account closure request', + ], + ]; + + $ticketStatuses = array_merge( + array_fill(0, 60, 'open'), + array_fill(0, 50, 'in_progress'), + array_fill(0, 40, 'waiting'), + array_fill(0, 50, 'closed'), + ); + shuffle($ticketStatuses); + + $priorities = ['low', 'low', 'medium', 'medium', 'medium', 'high', 'high', 'urgent']; + $total = 200; + + $ticketRows = []; + $ticketMeta = []; + + for ($i = 0; $i < $total; $i++) { + $customer = $customers->random(); + $status = $ticketStatuses[$i] ?? 'open'; + $department = $faker->randomElement(['billing', 'technical', 'sales', 'general']); + $priority = $priorities[array_rand($priorities)]; + $subjects = $ticketSubjects[$department]; + $subject = $subjects[array_rand($subjects)]; + + $createdAt = now()->subDays(rand(1, 180)); + $lastReplyAt = $status !== 'open' ? $createdAt->copy()->addDays(rand(0, 14)) : null; + + $ticketRows[] = [ + 'user_id' => $customer->id, + 'subject' => $subject, + 'status' => $status, + 'priority' => $priority, + 'department' => $department, + 'last_reply_at' => $lastReplyAt, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + + $ticketMeta[] = [ + 'user_id' => $customer->id, + 'department' => $department, + 'created_at' => $createdAt, + ]; + } + + foreach (array_chunk($ticketRows, 100) as $chunk) { + DB::table('support_tickets')->insert($chunk); + } + + // Get ticket IDs back + $ticketIds = DB::table('support_tickets') + ->whereIn('user_id', $customers->pluck('id')->toArray()) + ->orderBy('id') + ->pluck('id') + ->toArray(); + + // Limit to the tickets we just created (in case there were pre-existing ones) + $ticketIds = array_slice($ticketIds, -$total); + + // Create 2-5 replies per ticket + $replyTemplates = [ + 'customer' => [ + 'I need help with this issue urgently. Please assist.', + 'This is causing problems for my business. When can I expect a resolution?', + 'Thank you for looking into this. Any update on the status?', + 'I have tried the steps you suggested but the issue persists.', + 'Could you please provide more details on how to resolve this?', + 'The issue seems to have gotten worse since my last update.', + 'I appreciate your help. Let me know if you need more information.', + 'This has been ongoing for several days now. Please escalate.', + ], + 'staff' => [ + 'Thank you for reaching out. I am looking into this for you now.', + 'I have identified the issue and am working on a fix. I will update you shortly.', + 'Could you please provide your server IP and any error messages you are seeing?', + 'I have escalated this to our senior team. You should hear back within 24 hours.', + 'The issue has been resolved. Please confirm that everything is working correctly.', + 'I have applied a credit to your account for the inconvenience. Is there anything else?', + 'I have updated the firewall rules. Please try connecting again.', + 'This appears to be resolved on our end. Please let us know if you experience further issues.', + ], + ]; + + $replyRows = []; + + foreach ($ticketIds as $idx => $ticketId) { + $meta = $ticketMeta[$idx] ?? null; + if (! $meta) { + continue; + } + + $replyCount = rand(2, 5); + $replyDate = $meta['created_at']->copy(); + + for ($r = 0; $r < $replyCount; $r++) { + $isStaff = $r % 2 !== 0; // alternate customer/staff + $replyDate = $replyDate->copy()->addHours(rand(1, 48)); + + if ($replyDate->isFuture()) { + $replyDate = now()->subMinutes(rand(1, 60)); + } + + $templates = $isStaff ? $replyTemplates['staff'] : $replyTemplates['customer']; + + $replyRows[] = [ + 'ticket_id' => $ticketId, + 'user_id' => $isStaff ? $adminId : $meta['user_id'], + 'body' => $templates[array_rand($templates)], + 'is_staff_reply' => $isStaff, + 'created_at' => $replyDate, + 'updated_at' => $replyDate, + ]; + } + } + + foreach (array_chunk($replyRows, 200) as $chunk) { + DB::table('ticket_replies')->insert($chunk); + } + } + + /** + * Create ~50 coupons. + */ + private function createCoupons(): void + { + $rows = []; + $faker = fake(); + + $couponNames = [ + 'WELCOME', 'SUMMER', 'WINTER', 'SPRING', 'FALL', 'HOLIDAY', 'NEWYEAR', + 'BLACKFRI', 'CYBERMON', 'LAUNCH', 'EARLYBIRD', 'LOYALTY', 'REFER', + 'VPS10', 'VPS20', 'VPS30', 'HOST15', 'HOST25', 'DEDI10', 'DEDI20', + 'SAVE10', 'SAVE15', 'SAVE20', 'SAVE25', 'SAVE30', 'SAVE40', 'SAVE50', + 'FIRST50', 'ANNUAL20', 'MIGRATE', 'TRANSFER', 'STARTUP', 'STUDENT', + 'NONPROFIT', 'DEVOPS', 'CLOUD10', 'CLOUD20', 'CLOUD30', 'BF2025', + 'CM2025', 'NY2026', 'SPECIAL', 'FLASH', 'LIMITED', 'EXCLUSIVE', + 'PARTNER', 'AGENCY', 'RESELLER', 'BULK', 'ENTERPRISE', + ]; + + $total = 50; + + for ($i = 0; $i < $total; $i++) { + $type = $faker->randomElement(['percentage', 'percentage', 'fixed_amount']); + $value = $type === 'percentage' + ? $faker->randomElement([5, 10, 15, 20, 25, 30, 40, 50]) + : $faker->randomFloat(2, 2, 25); + + $isActive = $faker->boolean(70); + $expiresAt = $faker->optional(0.6)->dateTimeBetween('-3 months', '+6 months'); + $maxUses = $faker->optional(0.5)->numberBetween(10, 500); + $timesUsed = $maxUses ? $faker->numberBetween(0, (int) min($maxUses, 100)) : $faker->numberBetween(0, 50); + + $createdAt = now()->subDays(rand(1, 300)); + + $rows[] = [ + 'code' => $couponNames[$i] ?? strtoupper(Str::random(6)), + 'type' => $type, + 'value' => $value, + 'currency' => $type === 'fixed_amount' ? 'USD' : null, + 'applies_to' => $faker->optional(0.3)->randomElement([ + json_encode(['service_type' => 'vps']), + json_encode(['service_type' => 'dedicated']), + json_encode(['service_type' => 'hosting']), + ]), + 'max_uses' => $maxUses, + 'times_used' => $timesUsed, + 'active' => $isActive, + 'expires_at' => $expiresAt, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + foreach (array_chunk($rows, 50) as $chunk) { + DB::table('coupons')->insert($chunk); + } + } + + /** + * Create ~100 audit log entries. + * + * @param \Illuminate\Support\Collection $customers + */ + private function createAuditLogs(\Illuminate\Support\Collection $customers, int $adminId): void + { + $actions = [ + ['action' => 'user.created', 'resource_type' => 'User'], + ['action' => 'user.updated', 'resource_type' => 'User'], + ['action' => 'user.suspended', 'resource_type' => 'User'], + ['action' => 'user.banned', 'resource_type' => 'User'], + ['action' => 'user.reactivated', 'resource_type' => 'User'], + ['action' => 'service.provisioned', 'resource_type' => 'Service'], + ['action' => 'service.suspended', 'resource_type' => 'Service'], + ['action' => 'service.terminated', 'resource_type' => 'Service'], + ['action' => 'invoice.created', 'resource_type' => 'Invoice'], + ['action' => 'invoice.paid', 'resource_type' => 'Invoice'], + ['action' => 'invoice.voided', 'resource_type' => 'Invoice'], + ['action' => 'subscription.created', 'resource_type' => 'Subscription'], + ['action' => 'subscription.cancelled', 'resource_type' => 'Subscription'], + ['action' => 'coupon.created', 'resource_type' => 'Coupon'], + ['action' => 'coupon.deactivated', 'resource_type' => 'Coupon'], + ['action' => 'plan.updated', 'resource_type' => 'Plan'], + ['action' => 'order.completed', 'resource_type' => 'Order'], + ['action' => 'order.cancelled', 'resource_type' => 'Order'], + ['action' => 'impersonation.started', 'resource_type' => 'User'], + ['action' => 'impersonation.stopped', 'resource_type' => 'User'], + ['action' => 'settings.updated', 'resource_type' => 'Setting'], + ['action' => 'login', 'resource_type' => null], + ['action' => 'logout', 'resource_type' => null], + ]; + + $faker = fake(); + $rows = []; + $total = 100; + + $userAgents = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15', + ]; + + for ($i = 0; $i < $total; $i++) { + $actionDef = $actions[array_rand($actions)]; + $customer = $customers->random(); + $createdAt = now()->subDays(rand(1, 180)); + + $changes = null; + if (str_contains($actionDef['action'], 'updated') || str_contains($actionDef['action'], 'suspended') || str_contains($actionDef['action'], 'banned')) { + $changes = json_encode([ + 'old' => ['status' => 'active'], + 'new' => ['status' => $faker->randomElement(['suspended', 'banned', 'active'])], + ]); + } + + $rows[] = [ + 'user_id' => $customer->id, + 'admin_id' => $adminId, + 'action' => $actionDef['action'], + 'resource_type' => $actionDef['resource_type'], + 'resource_id' => $actionDef['resource_type'] ? $faker->numberBetween(1, 500) : null, + 'ip_address' => $faker->ipv4(), + 'user_agent' => $userAgents[array_rand($userAgents)], + 'changes' => $changes, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]; + } + + foreach (array_chunk($rows, 100) as $chunk) { + DB::table('audit_logs')->insert($chunk); + } + } +}