where('slug', 'trial')->firstOrFail(); $enterprise = SubscriptionPlan::query()->where('slug', 'enterprise')->firstOrFail(); // ========================================================= // 1. PEMILIK APLIKASI / PLATFORM ADMIN // ========================================================= $platform = $this->company( code: 'PLATFORM', name: 'CRM Sales & Distribution Platform', email: 'platform@crm.test', plan: $enterprise, expiresAt: now()->addYears(10)->toDateString(), subscriptionStatus: 'active', ); $platformRoleId = $this->role($platform->id, 'Platform Owner', 'platform-owner', ['all' => true]); $platformTeamId = $this->team($platform->id, 'Platform Team', 'Internal'); $this->user( perusahaanId: $platform->id, roleId: $platformRoleId, teamId: $platformTeamId, name: 'Pemilik Aplikasi', email: 'owner@crm.test', password: 'password', employeeCode: 'OWNER-001', isPlatformAdmin: true, ); // ========================================================= // 2. PERUSAHAAN DEMO / TENANT // ========================================================= $company = $this->company( code: 'DEMO', name: 'Perusahaan Demo', email: 'demo@crm.test', plan: $trial, expiresAt: now()->addDays(14)->toDateString(), subscriptionStatus: 'trial', ); $adminRoleId = $this->role($company->id, 'Admin', 'admin', ['all' => true]); $managerRoleId = $this->role($company->id, 'Manager', 'manager', [ 'web.access' => true, 'dashboard.view' => true, 'reports.view' => true, 'reports.visits.per_sales' => true, 'reports.visits.per_period' => true, 'customers.view' => true, 'sales.manage' => true, 'teams.manage' => true, 'areas.view' => true, 'areas.manage' => true, 'products.view' => true, 'orders.view' => true, 'invoices.view' => true, 'payments.view' => true, 'collections.manage' => true, 'visits.manage' => true, ]); $supervisorRoleId = $this->role($company->id, 'Supervisor', 'supervisor', [ 'web.access' => true, 'dashboard.view' => true, 'reports.view' => true, 'reports.visits.per_sales' => true, 'reports.visits.per_period' => true, 'customers.view' => true, 'sales.manage' => true, 'teams.manage' => true, 'areas.view' => true, 'areas.manage' => true, 'products.view' => true, 'orders.view' => true, 'collections.manage' => true, 'visits.manage' => true, ]); $adminPengirimanRoleId = $this->role($company->id, 'Admin Pengiriman', 'admin-pengiriman', [ 'web.access' => true, 'dashboard.view' => true, 'orders.view' => true, 'invoices.view' => true, 'deliveries.view' => true, 'deliveries.create' => true, 'deliveries.assign' => true, 'deliveries.update' => true, ]); $pengirimanRoleId = $this->role($company->id, 'Pengiriman', 'pengiriman', [ 'web.access' => true, 'dashboard.view' => true, 'deliveries.view' => true, 'deliveries.update' => true, ]); $salesRoleId = $this->role($company->id, 'Sales', 'sales', [ 'dashboard.view' => true, 'customers.view' => true, 'customers.create' => true, 'customers.edit' => true, 'products.view' => true, 'orders.view' => true, 'orders.create' => true, 'visits.manage' => true, ]); $teamId = $this->team($company->id, 'Team Management', 'Jakarta'); $salesTeamId = $this->team($company->id, 'Sales Team', 'Jakarta'); $deliveryTeamId = $this->team($company->id, 'Delivery Team', 'Jakarta'); $companyAdmin = $this->user( perusahaanId: $company->id, roleId: $adminRoleId, teamId: $teamId, name: 'Administrator Perusahaan', email: 'admin@demo.test', password: 'password', employeeCode: 'ADM-001', isPlatformAdmin: false, ); $managerUser = $this->user( perusahaanId: $company->id, roleId: $managerRoleId, teamId: $teamId, name: 'Manager Demo', email: 'manager@demo.test', password: 'password', employeeCode: 'MGR-001', isPlatformAdmin: false, ); $supervisorUser = $this->user( perusahaanId: $company->id, roleId: $supervisorRoleId, teamId: $salesTeamId, name: 'Supervisor Demo', email: 'supervisor@demo.test', password: 'password', employeeCode: 'SPV-001', isPlatformAdmin: false, ); $adminPengirimanUser = $this->user( perusahaanId: $company->id, roleId: $adminPengirimanRoleId, teamId: $deliveryTeamId, name: 'Admin Pengiriman Demo', email: 'admin.pengiriman@demo.test', password: 'password', employeeCode: 'APG-001', isPlatformAdmin: false, ); $pengirimanUser = $this->user( perusahaanId: $company->id, roleId: $pengirimanRoleId, teamId: $deliveryTeamId, name: 'Pengiriman Demo', email: 'pengiriman@demo.test', password: 'password', employeeCode: 'PG-001', isPlatformAdmin: false, ); $salesUser = $this->user( perusahaanId: $company->id, roleId: $salesRoleId, teamId: $salesTeamId, name: 'Sales Demo', email: 'sales@demo.test', password: 'password', employeeCode: 'SLS-001', isPlatformAdmin: false, ); // Tandai tenant demo sudah melewati onboarding agar langsung masuk dashboard. $company->update([ 'settings' => array_merge($company->settings ?? [], [ 'onboarding_completed' => true, 'onboarding_completed_at' => now()->toIso8601String(), 'business_type' => 'Distribusi FMCG', 'timezone' => 'Asia/Jakarta', 'currency' => 'IDR', ]), ]); // ========================================================= // 3. MASTER DATA DEMO // ========================================================= $this->seedDemoMasterData($company->id, $salesTeamId, $salesUser->id); $this->seedDemoTransactionalData($company->id, $companyAdmin->id, $salesUser->id); $this->seedDemoDeliveries($company->id, $adminPengirimanUser->id, $pengirimanUser->id); }); $this->command?->info('Seeder CRM berhasil dijalankan.'); $this->command?->line('PEMILIK APLIKASI : owner@crm.test / password'); $this->command?->line('ADMIN PERUSAHAAN : admin@demo.test / password'); $this->command?->line('SALES : sales@demo.test / password'); } private function seedDemoMasterData(int $companyId, int $salesTeamId, int $salesId): void { // Master area demo. Area menjadi referensi resmi untuk Team, Sales, dan Customer. $jakarta = $this->area($companyId, 'JKT', 'Jakarta', 'city'); $areaNames = [ 'Jakarta Selatan', 'Jakarta Timur', 'Jakarta Barat', 'Jakarta Pusat', 'Jakarta Utara', ]; $areaIds = []; foreach ($areaNames as $name) { $areaIds[$name] = $this->area($companyId, 'JKT-' . str_replace(' ', '-', strtoupper(substr($name, 8))), $name, 'district', $jakarta); } // Sales Demo dapat menangani seluruh area demo. foreach ($areaIds as $areaId) { DB::table('area_sales')->updateOrInsert( [ 'perusahaan_id' => $companyId, 'area_id' => $areaId, 'sales_id' => $salesId, ], ['updated_at' => now(), 'created_at' => now()] ); } $customerCategories = [ ['name' => 'Grosir', 'description' => 'Pelanggan grosir dan distributor kecil.'], ['name' => 'Retail', 'description' => 'Toko retail dan outlet.'], ['name' => 'Modern Trade', 'description' => 'Supermarket dan jaringan retail modern.'], ]; $categoryIds = []; foreach ($customerCategories as $category) { $existing = DB::table('customer_categories') ->where('perusahaan_id', $companyId) ->where('name', $category['name']) ->first(); if ($existing) { $categoryIds[$category['name']] = (int) $existing->id; DB::table('customer_categories')->where('id', $existing->id)->update([ 'description' => $category['description'], 'updated_at' => now(), ]); continue; } $categoryIds[$category['name']] = (int) DB::table('customer_categories')->insertGetId([ 'perusahaan_id' => $companyId, 'name' => $category['name'], 'description' => $category['description'], 'created_at' => now(), 'updated_at' => now(), ]); } $productCategories = [ ['name' => 'Minuman', 'description' => 'Produk minuman.'], ['name' => 'Makanan', 'description' => 'Produk makanan.'], ['name' => 'Kebutuhan Rumah Tangga', 'description' => 'Produk kebutuhan rumah tangga.'], ]; $productCategoryIds = []; foreach ($productCategories as $category) { $existing = DB::table('product_categories') ->where('perusahaan_id', $companyId) ->where('name', $category['name']) ->first(); if ($existing) { $productCategoryIds[$category['name']] = (int) $existing->id; continue; } $productCategoryIds[$category['name']] = (int) DB::table('product_categories')->insertGetId([ 'perusahaan_id' => $companyId, 'name' => $category['name'], 'description' => $category['description'], 'created_at' => now(), 'updated_at' => now(), ]); } $products = [ ['sku' => 'MIN-001', 'name' => 'Air Mineral 600ml', 'category' => 'Minuman', 'unit' => 'botol', 'price' => 4000, 'cost_price' => 2500, 'stock' => 500], ['sku' => 'MIN-002', 'name' => 'Teh Botol 350ml', 'category' => 'Minuman', 'unit' => 'botol', 'price' => 5000, 'cost_price' => 3200, 'stock' => 350], ['sku' => 'MAK-001', 'name' => 'Biskuit Original', 'category' => 'Makanan', 'unit' => 'pcs', 'price' => 8500, 'cost_price' => 6000, 'stock' => 250], ['sku' => 'MAK-002', 'name' => 'Keripik Pisang', 'category' => 'Makanan', 'unit' => 'pack', 'price' => 12000, 'cost_price' => 8000, 'stock' => 180], ['sku' => 'RMT-001', 'name' => 'Sabun Cuci Piring', 'category' => 'Kebutuhan Rumah Tangga', 'unit' => 'botol', 'price' => 11000, 'cost_price' => 7500, 'stock' => 200], ]; foreach ($products as $product) { DB::table('products')->updateOrInsert( ['perusahaan_id' => $companyId, 'sku' => $product['sku']], [ 'product_category_id' => $productCategoryIds[$product['category']], 'name' => $product['name'], 'description' => 'Data demo untuk pengujian CRM.', 'unit' => $product['unit'], 'price' => $product['price'], 'cost_price' => $product['cost_price'], 'stock' => $product['stock'], 'status' => 'active', 'updated_at' => now(), 'created_at' => now(), ] ); } $customers = [ ['code' => 'CUST-001', 'name' => 'Toko Sejahtera', 'category' => 'Grosir', 'phone' => '081200000001', 'address' => 'Jakarta Selatan'], ['code' => 'CUST-002', 'name' => 'CV Maju Bersama', 'category' => 'Grosir', 'phone' => '081200000002', 'address' => 'Jakarta Timur'], ['code' => 'CUST-003', 'name' => 'Toko Berkah', 'category' => 'Retail', 'phone' => '081200000003', 'address' => 'Jakarta Barat'], ['code' => 'CUST-004', 'name' => 'Supermart Demo', 'category' => 'Modern Trade', 'phone' => '081200000004', 'address' => 'Jakarta Pusat'], ['code' => 'CUST-005', 'name' => 'Outlet Nusantara', 'category' => 'Retail', 'phone' => '081200000005', 'address' => 'Jakarta Utara'], ]; foreach ($customers as $customer) { DB::table('customers')->updateOrInsert( ['perusahaan_id' => $companyId, 'code' => $customer['code']], [ 'customer_category_id' => $categoryIds[$customer['category']], 'team_id' => $salesTeamId, 'area_id' => $areaIds[$customer['address']] ?? null, 'sales_id' => $salesId, 'name' => $customer['name'], 'address' => $customer['address'], 'phone' => $customer['phone'], 'email' => strtolower(str_replace(' ', '.', $customer['name'])).'@demo.test', 'contact_person' => 'PIC Demo', 'type' => 'company', 'credit_limit' => 10000000, 'status' => 'active', 'notes' => 'Customer demo.', 'updated_at' => now(), 'created_at' => now(), ] ); } } /** * Data transaksi demo untuk functional/runtime testing. * Seeder ini sengaja membuat beberapa variasi status agar dashboard, * reports, analytics, collections, visits, invoices, dan payments * langsung memiliki data yang realistis. */ private function seedDemoTransactionalData(int $companyId, int $adminId, int $salesId): void { // Hapus data test transaksi yang dibuat seeder ini agar aman dijalankan ulang. DB::table('payments')->where('perusahaan_id', $companyId)->delete(); DB::table('collection_schedules')->where('perusahaan_id', $companyId)->delete(); DB::table('invoices')->where('perusahaan_id', $companyId)->delete(); DB::table('order_status_histories')->where('perusahaan_id', $companyId)->delete(); DB::table('order_items')->where('perusahaan_id', $companyId)->delete(); // orders dan visits saling memiliki FK. Lepaskan relasi order->visit // terlebih dahulu sebelum menghapus keduanya. DB::table('orders')->where('perusahaan_id', $companyId)->update(['visit_id' => null]); DB::table('visits')->where('perusahaan_id', $companyId)->update(['order_id' => null]); DB::table('orders')->where('perusahaan_id', $companyId)->delete(); DB::table('visits')->where('perusahaan_id', $companyId)->delete(); DB::table('visit_schedules')->where('perusahaan_id', $companyId)->delete(); DB::table('sales_targets')->where('perusahaan_id', $companyId)->delete(); DB::table('price_lists')->where('perusahaan_id', $companyId)->delete(); DB::table('notifications')->where('perusahaan_id', $companyId)->delete(); DB::table('activity_logs')->where('perusahaan_id', $companyId)->delete(); DB::table('subscription_invoices')->where('perusahaan_id', $companyId)->delete(); $products = DB::table('products') ->where('perusahaan_id', $companyId) ->orderBy('id') ->get() ->keyBy('sku'); $customers = DB::table('customers') ->where('perusahaan_id', $companyId) ->orderBy('id') ->get() ->keyBy('code'); if ($products->count() < 3 || $customers->count() < 5) { throw new \RuntimeException('Master data demo belum lengkap. Jalankan seed master data terlebih dahulu.'); } // --------------------------------------------------------- // Price list: harga khusus per customer dan kategori. // --------------------------------------------------------- DB::table('price_lists')->insert([ [ 'perusahaan_id' => $companyId, 'product_id' => $products['MIN-001']->id, 'customer_id' => $customers['CUST-001']->id, 'customer_category_id' => null, 'special_price' => 3500, 'discount_percent' => null, 'valid_from' => now()->subMonth()->toDateString(), 'valid_until' => now()->addMonths(3)->toDateString(), 'created_at' => now(), 'updated_at' => now(), ], [ 'perusahaan_id' => $companyId, 'product_id' => $products['MAK-001']->id, 'customer_id' => null, 'customer_category_id' => $customers['CUST-001']->customer_category_id, 'special_price' => null, 'discount_percent' => 5, 'valid_from' => now()->subMonth()->toDateString(), 'valid_until' => now()->addMonths(3)->toDateString(), 'created_at' => now(), 'updated_at' => now(), ], ]); // --------------------------------------------------------- // Sales target bulan berjalan + bulan sebelumnya. // --------------------------------------------------------- foreach ([now()->copy()->subMonth(), now()->copy()] as $period) { DB::table('sales_targets')->insert([ 'perusahaan_id' => $companyId, 'sales_id' => $salesId, 'year' => $period->year, 'month' => $period->month, 'target_amount' => 25000000, 'target_visits' => 40, 'target_orders' => 20, 'created_at' => now(), 'updated_at' => now(), ]); } // --------------------------------------------------------- // Visit schedules + visits. // --------------------------------------------------------- $visitRows = [ ['date' => now()->toDateString(), 'time' => '09:00:00', 'customer' => 'CUST-001', 'purpose' => 'order', 'status' => 'done', 'result' => 'order', 'visit_status' => 'completed'], ['date' => now()->toDateString(), 'time' => '11:00:00', 'customer' => 'CUST-002', 'purpose' => 'general', 'status' => 'done', 'result' => 'survey', 'visit_status' => 'completed'], ['date' => now()->toDateString(), 'time' => '14:00:00', 'customer' => 'CUST-003', 'purpose' => 'collection', 'status' => 'in_progress', 'result' => 'collection', 'visit_status' => 'ongoing'], ['date' => now()->subDay()->toDateString(), 'time' => '09:30:00', 'customer' => 'CUST-004', 'purpose' => 'order', 'status' => 'done', 'result' => 'order', 'visit_status' => 'completed'], ['date' => now()->subDay()->toDateString(), 'time' => '13:00:00', 'customer' => 'CUST-005', 'purpose' => 'general', 'status' => 'skipped', 'result' => 'skip', 'visit_status' => 'skipped'], ['date' => now()->subDays(3)->toDateString(), 'time' => '10:00:00', 'customer' => 'CUST-001', 'purpose' => 'order', 'status' => 'done', 'result' => 'order', 'visit_status' => 'completed'], ]; $visits = []; foreach ($visitRows as $i => $row) { $scheduleId = DB::table('visit_schedules')->insertGetId([ 'perusahaan_id' => $companyId, 'sales_id' => $salesId, 'customer_id' => $customers[$row['customer']]->id, 'created_by' => $adminId, 'scheduled_date' => $row['date'], 'scheduled_time' => $row['time'], 'purpose' => $row['purpose'], 'status' => $row['status'], 'notes' => $row['visit_status'] === 'skipped' ? 'Customer tidak tersedia.' : 'Kunjungan demo untuk pengujian.', 'created_at' => now()->subDays($i), 'updated_at' => now(), ]); $checkin = $row['visit_status'] === 'skipped' ? null : $row['date'].' '.$row['time']; $checkout = $row['visit_status'] === 'completed' ? $row['date'].' '.date('H:i:s', strtotime($row['time'].' +45 minutes')) : null; $visitId = DB::table('visits')->insertGetId([ 'perusahaan_id' => $companyId, 'visit_schedule_id' => $scheduleId, 'sales_id' => $salesId, 'customer_id' => $customers[$row['customer']]->id, 'checkin_at' => $checkin, 'checkin_lat' => -6.2000000 + ($i * 0.001), 'checkin_lng' => 106.8166667 + ($i * 0.001), 'checkout_at' => $checkout, 'checkout_lat' => $checkout ? -6.2000000 + ($i * 0.001) : null, 'checkout_lng' => $checkout ? 106.8166667 + ($i * 0.001) : null, 'result_type' => $row['result'], 'status' => $row['visit_status'], 'skip_reason' => $row['visit_status'] === 'skipped' ? 'Toko tutup.' : null, 'order_id' => null, 'created_at' => now()->subDays($i), 'updated_at' => now(), ]); $visits[$i] = $visitId; } // --------------------------------------------------------- // Orders dengan variasi status untuk menguji seluruh badge/report. // --------------------------------------------------------- $orderDefinitions = [ ['no' => 'ORD-DEMO-001', 'customer' => 'CUST-001', 'visit' => $visits[0], 'date' => now()->toDateString(), 'status' => 'lunas', 'items' => [['sku' => 'MIN-001', 'qty' => 20], ['sku' => 'MAK-001', 'qty' => 10]]], ['no' => 'ORD-DEMO-002', 'customer' => 'CUST-004', 'visit' => $visits[3], 'date' => now()->subDay()->toDateString(), 'status' => 'shipped', 'items' => [['sku' => 'MIN-002', 'qty' => 30], ['sku' => 'MAK-002', 'qty' => 10]]], ['no' => 'ORD-DEMO-003', 'customer' => 'CUST-002', 'visit' => null, 'date' => now()->toDateString(), 'status' => 'approved', 'items' => [['sku' => 'RMT-001', 'qty' => 15], ['sku' => 'MAK-001', 'qty' => 20]]], ['no' => 'ORD-DEMO-004', 'customer' => 'CUST-003', 'visit' => null, 'date' => now()->subDays(2)->toDateString(), 'status' => 'submitted', 'items' => [['sku' => 'MIN-001', 'qty' => 12]]], ['no' => 'ORD-DEMO-005', 'customer' => 'CUST-005', 'visit' => null, 'date' => now()->subDays(4)->toDateString(), 'status' => 'draft', 'items' => [['sku' => 'MAK-002', 'qty' => 8]]], ['no' => 'ORD-DEMO-006', 'customer' => 'CUST-003', 'visit' => null, 'date' => now()->subDays(5)->toDateString(), 'status' => 'rejected', 'items' => [['sku' => 'MIN-002', 'qty' => 10]]], ['no' => 'ORD-DEMO-007', 'customer' => 'CUST-002', 'visit' => $visits[5], 'date' => now()->subDays(3)->toDateString(), 'status' => 'cancelled', 'items' => [['sku' => 'RMT-001', 'qty' => 5]]], ]; $orders = []; foreach ($orderDefinitions as $definition) { $subtotal = 0; foreach ($definition['items'] as $item) { $subtotal += (float) $products[$item['sku']]->price * $item['qty']; } $discount = $definition['status'] === 'approved' ? 5000 : 0; $tax = round(($subtotal - $discount) * 0.11, 2); $grandTotal = $subtotal - $discount + $tax; $orderId = DB::table('orders')->insertGetId([ 'perusahaan_id' => $companyId, 'customer_id' => $customers[$definition['customer']]->id, 'sales_id' => $salesId, 'visit_id' => $definition['visit'], 'approved_by' => in_array($definition['status'], ['approved', 'shipped', 'lunas'], true) ? $adminId : null, 'order_number' => $definition['no'], 'order_date' => $definition['date'], 'subtotal' => $subtotal, 'discount_total' => $discount, 'tax_total' => $tax, 'grand_total' => $grandTotal, 'status' => $definition['status'], 'approved_at' => in_array($definition['status'], ['approved', 'shipped', 'lunas'], true) ? now()->subDays(1) : null, 'notes' => 'Order demo untuk functional testing.', 'created_at' => now()->subDays(7), 'updated_at' => now(), ]); foreach ($definition['items'] as $item) { $unitPrice = (float) $products[$item['sku']]->price; $itemSubtotal = $unitPrice * $item['qty']; DB::table('order_items')->insert([ 'perusahaan_id' => $companyId, 'order_id' => $orderId, 'product_id' => $products[$item['sku']]->id, 'qty' => $item['qty'], 'unit_price' => $unitPrice, 'discount_amount' => 0, 'subtotal' => $itemSubtotal, 'created_at' => now(), 'updated_at' => now(), ]); } $orders[$definition['no']] = ['id' => $orderId, 'total' => $grandTotal, 'status' => $definition['status'], 'customer_id' => $customers[$definition['customer']]->id]; $history = match ($definition['status']) { 'submitted' => [['draft', 'submitted']], 'approved' => [['draft', 'submitted'], ['submitted', 'approved']], 'shipped' => [['draft', 'submitted'], ['submitted', 'approved'], ['approved', 'shipped']], 'lunas' => [['draft', 'submitted'], ['submitted', 'approved'], ['approved', 'shipped'], ['shipped', 'lunas']], 'rejected' => [['draft', 'submitted'], ['submitted', 'rejected']], 'cancelled' => [['draft', 'cancelled']], default => [], }; foreach ($history as [$from, $to]) { DB::table('order_status_histories')->insert([ 'perusahaan_id' => $companyId, 'order_id' => $orderId, 'from_status' => $from, 'to_status' => $to, 'changed_by' => $to === 'approved' ? $adminId : $salesId, 'remarks' => 'Riwayat status demo.', 'created_at' => now()->subDays(2), 'updated_at' => now()->subDays(2), ]); } } // Kaitkan order yang lahir dari visit. DB::table('visits')->where('id', $visits[0])->update(['order_id' => $orders['ORD-DEMO-001']['id']]); DB::table('visits')->where('id', $visits[3])->update(['order_id' => $orders['ORD-DEMO-002']['id']]); DB::table('visits')->where('id', $visits[5])->update(['order_id' => $orders['ORD-DEMO-007']['id']]); // --------------------------------------------------------- // Invoice + payment: unpaid, overdue, partial, paid. // --------------------------------------------------------- $invoiceDefinitions = [ ['no' => 'INV-DEMO-001', 'order' => 'ORD-DEMO-001', 'invoice_date' => now()->subDays(10), 'due_date' => now()->subDays(3), 'status' => 'paid', 'paid' => null], ['no' => 'INV-DEMO-002', 'order' => 'ORD-DEMO-002', 'invoice_date' => now()->subDays(7), 'due_date' => now()->addDays(7), 'status' => 'unpaid', 'paid' => 0], ['no' => 'INV-DEMO-003', 'order' => 'ORD-DEMO-003', 'invoice_date' => now()->subDays(5), 'due_date' => now()->addDays(10), 'status' => 'partial', 'paid' => null], ['no' => 'INV-DEMO-004', 'order' => 'ORD-DEMO-004', 'invoice_date' => now()->subDays(20), 'due_date' => now()->subDays(5), 'status' => 'overdue', 'paid' => 0], ]; foreach ($invoiceDefinitions as $definition) { $order = $orders[$definition['order']]; $invoiceId = DB::table('invoices')->insertGetId([ 'perusahaan_id' => $companyId, 'order_id' => $order['id'], 'customer_id' => $order['customer_id'], 'invoice_number' => $definition['no'], 'amount' => $order['total'], 'paid_amount' => 0, 'invoice_date' => $definition['invoice_date']->toDateString(), 'due_date' => $definition['due_date']->toDateString(), 'status' => $definition['status'], 'created_at' => now()->subDays(5), 'updated_at' => now(), ]); $paid = $definition['paid']; if ($definition['status'] === 'paid') { $paid = $order['total']; } elseif ($definition['status'] === 'partial') { $paid = round($order['total'] * 0.4, 2); } if ($paid > 0) { DB::table('payments')->insert([ 'perusahaan_id' => $companyId, 'invoice_id' => $invoiceId, 'collector_id' => $salesId, 'amount' => $paid, 'payment_date' => now()->subDays(1)->toDateString(), 'method' => $definition['status'] === 'paid' ? 'transfer' : 'cash', 'proof_photo' => null, 'notes' => 'Pembayaran demo untuk functional testing.', 'created_at' => now()->subDay(), 'updated_at' => now(), ]); } DB::table('invoices')->where('id', $invoiceId)->update(['paid_amount' => $paid, 'updated_at' => now()]); } // --------------------------------------------------------- // Collection schedules untuk halaman Collections. // --------------------------------------------------------- $invoices = DB::table('invoices')->where('perusahaan_id', $companyId)->get(); foreach ($invoices as $index => $invoice) { DB::table('collection_schedules')->insert([ 'perusahaan_id' => $companyId, 'collector_id' => $salesId, 'invoice_id' => $invoice->id, 'customer_id' => $invoice->customer_id, 'scheduled_date' => $index === 0 ? now()->toDateString() : now()->addDays($index)->toDateString(), 'status' => $index === 0 ? 'collected' : ($index === 1 ? 'planned' : 'visited'), 'notes' => 'Jadwal penagihan demo.', 'created_at' => now(), 'updated_at' => now(), ]); } // --------------------------------------------------------- // Notification + activity log untuk UI header dan audit. // --------------------------------------------------------- DB::table('notifications')->insert([ 'perusahaan_id' => $companyId, 'user_id' => $adminId, 'type' => 'order', 'title' => 'Order baru menunggu perhatian', 'message' => 'ORD-DEMO-004 masih berstatus submitted.', 'data' => json_encode(['order_number' => 'ORD-DEMO-004']), 'read_at' => null, 'created_at' => now(), 'updated_at' => now(), ]); DB::table('notifications')->insert([ 'perusahaan_id' => $companyId, 'user_id' => $adminId, 'type' => 'payment', 'title' => 'Invoice jatuh tempo', 'message' => 'INV-DEMO-004 membutuhkan tindak lanjut collection.', 'data' => json_encode(['invoice_number' => 'INV-DEMO-004']), 'read_at' => null, 'created_at' => now()->subHour(), 'updated_at' => now()->subHour(), ]); DB::table('activity_logs')->insert([ 'perusahaan_id' => $companyId, 'user_id' => $adminId, 'action' => 'created', 'subject_type' => 'App\\Models\\Order', 'subject_id' => $orders['ORD-DEMO-001']['id'], 'old_values' => null, 'new_values' => json_encode(['order_number' => 'ORD-DEMO-001', 'status' => 'lunas']), 'ip_address' => '127.0.0.1', 'created_at' => now()->subMinutes(30), 'updated_at' => now()->subMinutes(30), ]); DB::table('activity_logs')->insert([ 'perusahaan_id' => $companyId, 'user_id' => $salesId, 'action' => 'checkin', 'subject_type' => 'App\\Models\\Visit', 'subject_id' => $visits[0], 'old_values' => null, 'new_values' => json_encode(['customer' => 'Toko Sejahtera']), 'ip_address' => '127.0.0.1', 'created_at' => now()->subMinutes(20), 'updated_at' => now()->subMinutes(20), ]); // --------------------------------------------------------- // Subscription invoice untuk halaman Billing. // --------------------------------------------------------- $trial = DB::table('subscription_plans')->where('slug', 'trial')->first(); if ($trial) { DB::table('subscription_invoices')->insert([ 'perusahaan_id' => $companyId, 'subscription_plan_id' => $trial->id, 'invoice_number' => 'SUB-DEMO-001', 'amount' => 0, 'status' => 'paid', 'issued_at' => now()->subDays(10), 'due_at' => now()->subDays(3), 'paid_at' => now()->subDays(9), 'payment_provider' => 'manual', 'external_reference' => 'DEMO-TRIAL', 'payment_url' => null, 'gateway_response' => json_encode(['demo' => true]), 'created_at' => now()->subDays(10), 'updated_at' => now()->subDays(9), ]); } $this->command?->line(' ✓ Demo transactional data: visits, orders, invoices, payments, collections, reports, notifications, audit logs.'); } private function seedDemoDeliveries(int $companyId, int $adminPengirimanId, int $pengirimanId): void { $orders = DB::table('orders')->where('perusahaan_id', $companyId)->orderBy('id')->limit(4)->get(); $statuses = ['pending', 'assigned', 'in_transit', 'delivered']; foreach ($orders as $i => $order) { $status = $statuses[$i] ?? 'pending'; $existing = DB::table('deliveries')->where('perusahaan_id', $companyId)->where('order_id', $order->id)->first(); if ($existing) continue; $customer = DB::table('customers')->where('id', $order->customer_id)->first(); $courierId = $status === 'pending' ? null : $pengirimanId; $now = now(); DB::table('deliveries')->insert([ 'perusahaan_id' => $companyId, 'order_id' => $order->id, 'courier_id' => $courierId, 'delivery_number' => 'SJ-DEMO-' . str_pad((string)($i + 1), 3, '0', STR_PAD_LEFT), 'delivery_address' => $customer->address ?? null, 'recipient_name' => $customer->name ?? null, 'recipient_phone' => $customer->phone ?? null, 'scheduled_date' => now()->addDays($i)->toDateString(), 'status' => $status, 'assigned_at' => $courierId ? $now : null, 'picked_up_at' => in_array($status, ['in_transit','delivered'], true) ? $now : null, 'in_transit_at' => $status === 'delivered' ? $now : null, 'delivered_at' => $status === 'delivered' ? $now : null, 'notes' => 'Surat jalan demo untuk pengujian workflow pengiriman.', 'created_at' => $now, 'updated_at' => $now, ]); } } private function company( string $code, string $name, string $email, SubscriptionPlan $plan, string $expiresAt, string $subscriptionStatus, ): Perusahaan { $company = Perusahaan::withTrashed()->firstOrNew(['kode_perusahaan' => $code]); if ($company->trashed()) { $company->restore(); } $company->fill([ 'nama' => $name, 'email' => $email, 'telepon' => $code === 'DEMO' ? '081234567890' : null, 'alamat' => $code === 'DEMO' ? 'Jakarta, Indonesia' : 'Internal Platform', 'status' => 'active', 'subscription_plan_id' => $plan->id, 'subscription_started_at' => now(), 'subscription_expires_at' => $expiresAt, 'subscription_status' => $subscriptionStatus, 'subscription_grace_until' => null, 'settings' => $company->settings ?? [], ]); $company->save(); return $company; } private function role(int $companyId, string $name, string $slug, array $permissions): int { $existing = DB::table('roles') ->where('perusahaan_id', $companyId) ->where('slug', $slug) ->first(); $data = [ 'perusahaan_id' => $companyId, 'name' => $name, 'slug' => $slug, 'permissions' => json_encode($permissions), 'updated_at' => now(), ]; if ($existing) { DB::table('roles')->where('id', $existing->id)->update($data); return (int) $existing->id; } return (int) DB::table('roles')->insertGetId($data + ['created_at' => now()]); } private function team(int $companyId, string $name, ?string $area = null): int { $areaId = null; if ($area) { $areaId = $this->area($companyId, 'AUTO-' . strtoupper(substr(sha1($area), 0, 8)), $area, 'territory'); } $existing = DB::table('teams') ->where('perusahaan_id', $companyId) ->where('name', $name) ->first(); $data = [ 'perusahaan_id' => $companyId, 'name' => $name, 'area' => $area, 'area_id' => $areaId, 'updated_at' => now(), ]; if ($existing) { DB::table('teams')->where('id', $existing->id)->update($data); return (int) $existing->id; } return (int) DB::table('teams')->insertGetId($data + ['created_at' => now()]); } private function area( int $companyId, string $code, string $name, string $type = 'territory', ?int $parentId = null, ): int { $existing = DB::table('areas') ->where('perusahaan_id', $companyId) ->where('name', $name) ->first(); $data = [ 'perusahaan_id' => $companyId, 'parent_id' => $parentId, 'code' => $code, 'name' => $name, 'type' => $type, 'is_active' => true, 'updated_at' => now(), ]; if ($existing) { DB::table('areas')->where('id', $existing->id)->update($data); return (int) $existing->id; } return (int) DB::table('areas')->insertGetId($data + ['created_at' => now()]); } private function user( int $perusahaanId, int $roleId, int $teamId, string $name, string $email, string $password, string $employeeCode, bool $isPlatformAdmin, ): User { return User::withTrashed()->updateOrCreate( ['perusahaan_id' => $perusahaanId, 'email' => $email], [ 'name' => $name, 'password' => Hash::make($password), 'phone' => null, 'employee_code' => $employeeCode, 'status' => 'active', 'email_verified_at' => now(), 'role_id' => $roleId, 'team_id' => $teamId, 'is_platform_admin' => $isPlatformAdmin, 'deleted_at' => null, ], ); } }