Add DemoDataSeeder with 300 customers and realistic data

Generates 300 customers with profiles, 500 subscriptions, 400 services,
800 invoices, 600 transactions, 150 orders, 200 tickets with ~700
replies, 50 coupons, and 100 audit log entries. Data spread across 12
months with realistic plan linkage and varied statuses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-09 17:10:12 -05:00
parent 015949a7f6
commit 6869a267ea
2 changed files with 865 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ class DatabaseSeeder extends Seeder
RoleAndPermissionSeeder::class,
PlanSeeder::class,
AdminUserSeeder::class,
DemoDataSeeder::class,
]);
}
}

View File

@@ -0,0 +1,864 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Invoice;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DemoDataSeeder extends Seeder
{
/**
* Marker email domain used for idempotency checks.
*/
private const DEMO_DOMAIN = 'demo.ezscale.test';
public function run(): void
{
// Idempotency: skip if demo data already exists
if (User::query()->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<int, User>
*/
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<int, User> $customers
* @param \Illuminate\Support\Collection<int, Plan> $plans
* @return array<int, array{user_id: int, plan_id: int}>
*/
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<int, User> $customers
* @param \Illuminate\Support\Collection<int, Plan> $plans
* @param array<int, array{user_id: int, plan_id: int}> $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<int, User> $customers
* @param \Illuminate\Support\Collection<int, Plan> $plans
* @param array<int, array{user_id: int, plan_id: int}> $subscriptionMap
* @return array<int, array{id: int, user_id: int, total: float, status: string}>
*/
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<int, User> $customers
* @param array<int, array{id: int, user_id: int, total: float, status: string}> $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<int, User> $customers
* @param \Illuminate\Support\Collection<int, Plan> $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<int, User> $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<int, User> $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);
}
}
}