seedDepartments($tenantId, $now); // 2. 거래처 $clientIds = $this->seedClients($tenantId, $now); // 3. 품목 (원자재, 반제품, 완제품) $this->seedItems($tenantId, $now); // 4. 견적 $this->seedQuotes($tenantId, $clientIds, $now); // 5. 수주 $this->seedOrders($tenantId, $clientIds, $now); // 6. 통계 데이터 (대시보드 차트용, 최근 90일) $this->seedStatData($tenantId); } private function seedDepartments(int $tenantId, $now): void { $parentId = DB::table('departments')->insertGetId([ 'tenant_id' => $tenantId, 'name' => '데모제조(주)', 'parent_id' => null, 'is_active' => true, 'sort_order' => 0, 'created_at' => $now, 'updated_at' => $now, ]); $departments = [ ['name' => '경영지원팀', 'sort_order' => 1], ['name' => '영업팀', 'sort_order' => 2], ['name' => '생산팀', 'sort_order' => 3], ['name' => '품질관리팀', 'sort_order' => 4], ['name' => '자재팀', 'sort_order' => 5], ]; foreach ($departments as $dept) { DB::table('departments')->insert([ 'tenant_id' => $tenantId, 'name' => $dept['name'], 'parent_id' => $parentId, 'is_active' => true, 'sort_order' => $dept['sort_order'], 'created_at' => $now, 'updated_at' => $now, ]); } } private function seedClients(int $tenantId, $now): array { $clients = [ ['code' => 'C-DEMO-001', 'name' => '(주)한국철강', 'type' => 'SUPPLIER', 'business_no' => '123-45-67890', 'phone' => '02-1234-5678'], ['code' => 'C-DEMO-002', 'name' => '삼성전자(주)', 'type' => 'CUSTOMER', 'business_no' => '234-56-78901', 'phone' => '02-2345-6789'], ['code' => 'C-DEMO-003', 'name' => '(주)대한물산', 'type' => 'CUSTOMER', 'business_no' => '345-67-89012', 'phone' => '02-3456-7890'], ['code' => 'C-DEMO-004', 'name' => '현대부품(주)', 'type' => 'SUPPLIER', 'business_no' => '456-78-90123', 'phone' => '031-4567-8901'], ['code' => 'C-DEMO-005', 'name' => '동양기계(주)', 'type' => 'BOTH', 'business_no' => '567-89-01234', 'phone' => '032-5678-9012'], ['code' => 'C-DEMO-006', 'name' => '(주)서울유통', 'type' => 'CUSTOMER', 'business_no' => '678-90-12345', 'phone' => '02-6789-0123'], ['code' => 'C-DEMO-007', 'name' => '부산산업(주)', 'type' => 'SUPPLIER', 'business_no' => '789-01-23456', 'phone' => '051-7890-1234'], ['code' => 'C-DEMO-008', 'name' => '(주)글로벌테크', 'type' => 'CUSTOMER', 'business_no' => '890-12-34567', 'phone' => '02-8901-2345'], ['code' => 'C-DEMO-009', 'name' => '인천금속(주)', 'type' => 'SUPPLIER', 'business_no' => '901-23-45678', 'phone' => '032-9012-3456'], ['code' => 'C-DEMO-010', 'name' => '(주)한빛전자', 'type' => 'CUSTOMER', 'business_no' => '012-34-56789', 'phone' => '02-0123-4567'], ]; $ids = []; foreach ($clients as $client) { $ids[] = DB::table('clients')->insertGetId([ 'tenant_id' => $tenantId, 'client_code' => $client['code'], 'name' => $client['name'], 'client_type' => $client['type'], 'business_no' => $client['business_no'], 'phone' => $client['phone'], 'is_active' => true, 'created_at' => $now, 'updated_at' => $now, ]); } return $ids; } private function seedItems(int $tenantId, $now): void { // 원자재 $rawMaterials = [ ['code' => 'RM-001', 'name' => 'STS304 스테인리스 판재 (1.0t)', 'unit' => 'EA'], ['code' => 'RM-002', 'name' => 'STS304 스테인리스 판재 (1.5t)', 'unit' => 'EA'], ['code' => 'RM-003', 'name' => '알루미늄 프로파일 40x40', 'unit' => 'M'], ['code' => 'RM-004', 'name' => '알루미늄 프로파일 60x60', 'unit' => 'M'], ['code' => 'RM-005', 'name' => 'SS400 철판 (3.0t)', 'unit' => 'EA'], ['code' => 'RM-006', 'name' => '동파이프 15A', 'unit' => 'M'], ['code' => 'RM-007', 'name' => '볼트 M8x20 STS', 'unit' => 'EA'], ['code' => 'RM-008', 'name' => '볼트 M10x30 STS', 'unit' => 'EA'], ['code' => 'RM-009', 'name' => '고무 패킹 (50mm)', 'unit' => 'EA'], ['code' => 'RM-010', 'name' => '에폭시 도료 (20L)', 'unit' => 'CAN'], ]; foreach ($rawMaterials as $rm) { DB::table('items')->insert([ 'tenant_id' => $tenantId, 'code' => $rm['code'], 'name' => $rm['name'], 'unit' => $rm['unit'], 'item_type' => 'MATERIAL', 'is_active' => true, 'created_at' => $now, 'updated_at' => $now, ]); } // 반제품 $subAssemblies = [ ['code' => 'SA-001', 'name' => '프레임 조립체 A', 'unit' => 'EA'], ['code' => 'SA-002', 'name' => '프레임 조립체 B', 'unit' => 'EA'], ['code' => 'SA-003', 'name' => '구동부 어셈블리', 'unit' => 'SET'], ['code' => 'SA-004', 'name' => '제어 패널 유닛', 'unit' => 'EA'], ['code' => 'SA-005', 'name' => '하우징 케이스', 'unit' => 'EA'], ]; foreach ($subAssemblies as $sa) { DB::table('items')->insert([ 'tenant_id' => $tenantId, 'code' => $sa['code'], 'name' => $sa['name'], 'unit' => $sa['unit'], 'item_type' => 'SUBASSEMBLY', 'is_active' => true, 'created_at' => $now, 'updated_at' => $now, ]); } // 완제품 $finishedProducts = [ ['code' => 'FP-001', 'name' => '자동화 컨베이어 시스템 (표준형)', 'unit' => 'SET'], ['code' => 'FP-002', 'name' => '자동화 컨베이어 시스템 (고속형)', 'unit' => 'SET'], ['code' => 'FP-003', 'name' => '산업용 로봇암 (6축)', 'unit' => 'EA'], ['code' => 'FP-004', 'name' => '정밀 CNC 가공기', 'unit' => 'EA'], ['code' => 'FP-005', 'name' => '유압 프레스 (50톤)', 'unit' => 'EA'], ]; foreach ($finishedProducts as $p) { DB::table('items')->insert([ 'tenant_id' => $tenantId, 'code' => $p['code'], 'name' => $p['name'], 'unit' => $p['unit'], 'item_type' => 'PRODUCT', 'is_active' => true, 'created_at' => $now, 'updated_at' => $now, ]); } } private function seedQuotes(int $tenantId, array $clientIds, $now): void { $quotes = [ ['no' => 'QT-2026-001', 'client_idx' => 1, 'name' => '컨베이어 시스템 표준형', 'qty' => 2, 'amount' => 45000000, 'status' => 'approved'], ['no' => 'QT-2026-002', 'client_idx' => 2, 'name' => '로봇암 6축 3대', 'qty' => 3, 'amount' => 120000000, 'status' => 'sent'], ['no' => 'QT-2026-003', 'client_idx' => 5, 'name' => 'CNC 가공기 설치', 'qty' => 1, 'amount' => 85000000, 'status' => 'finalized'], ['no' => 'QT-2026-004', 'client_idx' => 7, 'name' => '유압프레스 2대', 'qty' => 2, 'amount' => 60000000, 'status' => 'draft'], ['no' => 'QT-2026-005', 'client_idx' => 9, 'name' => '컨베이어 고속형', 'qty' => 1, 'amount' => 55000000, 'status' => 'converted'], ]; foreach ($quotes as $q) { $supplyAmount = $q['amount']; $taxAmount = (int) round($supplyAmount * 0.1); DB::table('quotes')->insert([ 'tenant_id' => $tenantId, 'quote_number' => $q['no'], 'quote_type' => 'manufacturing', 'registration_date' => now()->subDays(rand(10, 60))->toDateString(), 'client_id' => $clientIds[$q['client_idx']] ?? $clientIds[0], 'product_category' => 'STEEL', 'product_name' => $q['name'], 'quantity' => $q['qty'], 'subtotal' => $supplyAmount, 'discount_rate' => 0, 'discount_amount' => 0, 'total_amount' => $supplyAmount + $taxAmount, 'status' => $q['status'], 'current_revision' => 1, 'is_final' => $q['status'] === 'finalized' || $q['status'] === 'converted', 'created_at' => $now, 'updated_at' => $now, ]); } } private function seedOrders(int $tenantId, array $clientIds, $now): void { $orders = [ ['no' => 'SO-2026-001', 'client_idx' => 1, 'status' => 'CONFIRMED', 'qty' => 2, 'supply' => 45000000], ['no' => 'SO-2026-002', 'client_idx' => 2, 'status' => 'IN_PRODUCTION', 'qty' => 3, 'supply' => 120000000], ['no' => 'SO-2026-003', 'client_idx' => 5, 'status' => 'COMPLETED', 'qty' => 1, 'supply' => 85000000], ['no' => 'SO-2026-004', 'client_idx' => 7, 'status' => 'SHIPPED', 'qty' => 2, 'supply' => 60000000], ['no' => 'SO-2026-005', 'client_idx' => 9, 'status' => 'DRAFT', 'qty' => 1, 'supply' => 55000000], ['no' => 'SO-2026-006', 'client_idx' => 1, 'status' => 'CONFIRMED', 'qty' => 5, 'supply' => 30000000], ['no' => 'SO-2026-007', 'client_idx' => 2, 'status' => 'IN_PRODUCTION', 'qty' => 1, 'supply' => 95000000], ['no' => 'SO-2026-008', 'client_idx' => 5, 'status' => 'CONFIRMED', 'qty' => 4, 'supply' => 72000000], ]; foreach ($orders as $o) { $taxAmount = (int) round($o['supply'] * 0.1); DB::table('orders')->insert([ 'tenant_id' => $tenantId, 'order_no' => $o['no'], 'order_type_code' => 'ORDER', 'status_code' => $o['status'], 'client_id' => $clientIds[$o['client_idx']] ?? $clientIds[0], 'quantity' => $o['qty'], 'supply_amount' => $o['supply'], 'tax_amount' => $taxAmount, 'total_amount' => $o['supply'] + $taxAmount, 'received_at' => now()->subDays(rand(5, 45))->toDateString(), 'delivery_date' => now()->addDays(rand(10, 60))->toDateString(), 'created_at' => $now, 'updated_at' => $now, ]); } } private function seedStatData(int $tenantId): void { // sam_stat DB에 통계 데이터가 있어야 대시보드 차트가 의미있게 보인다. // 최근 90일치 일간 매출/생산 통계를 시드한다. $statConnection = 'sam_stat'; // sam_stat 연결이 설정되어 있지 않으면 스킵 if (! config("database.connections.{$statConnection}")) { return; } // 테이블 존재 여부 확인 try { $hasSalesTable = \Schema::connection($statConnection)->hasTable('stat_sales_daily'); $hasProductionTable = \Schema::connection($statConnection)->hasTable('stat_production_daily'); } catch (\Exception $e) { return; // sam_stat DB 접속 불가 시 스킵 } $today = now()->startOfDay(); // 매출 통계 (90일) if ($hasSalesTable) { $salesData = []; for ($i = 89; $i >= 0; $i--) { $date = $today->copy()->subDays($i); // 주말은 거래 없음 if ($date->isWeekend()) { continue; } // 계절성 반영 — 월초에 주문 많고, 중반에 매출 확정 $dayOfMonth = $date->day; $orderBase = ($dayOfMonth <= 10) ? rand(2, 5) : rand(0, 3); $salesBase = ($dayOfMonth >= 10 && $dayOfMonth <= 25) ? rand(1, 4) : rand(0, 2); // 성장 트렌드 (최근일수록 약간 증가) $growthFactor = 1.0 + (90 - $i) * 0.003; $orderCount = (int) round($orderBase * $growthFactor); $orderAmount = $orderCount * rand(15000000, 65000000); $salesCount = (int) round($salesBase * $growthFactor); $salesAmount = $salesCount * rand(20000000, 70000000); $salesData[] = [ 'tenant_id' => $tenantId, 'stat_date' => $date->toDateString(), 'order_count' => $orderCount, 'order_amount' => $orderAmount, 'order_item_count' => $orderCount * rand(2, 8), 'sales_count' => $salesCount, 'sales_amount' => $salesAmount, 'sales_tax_amount' => (int) round($salesAmount * 0.1), 'new_client_count' => rand(0, 1), 'active_client_count' => rand(5, 10), 'order_draft_count' => rand(0, 2), 'order_confirmed_count' => rand(1, 3), 'order_in_progress_count' => rand(1, 4), 'order_completed_count' => rand(0, 2), 'order_cancelled_count' => rand(0, 1) > 0 ? 0 : rand(0, 1), 'shipment_count' => $salesCount, 'shipment_amount' => $salesAmount, 'created_at' => now(), 'updated_at' => now(), ]; } // 기존 데이터 삭제 후 삽입 DB::connection($statConnection)->table('stat_sales_daily') ->where('tenant_id', $tenantId) ->delete(); // 청크 단위 삽입 foreach (array_chunk($salesData, 30) as $chunk) { DB::connection($statConnection)->table('stat_sales_daily')->insert($chunk); } } // 생산 통계 (90일) if ($hasProductionTable) { $prodData = []; for ($i = 89; $i >= 0; $i--) { $date = $today->copy()->subDays($i); if ($date->isWeekend()) { continue; } $growthFactor = 1.0 + (90 - $i) * 0.002; $productionQty = (int) round(rand(50, 150) * $growthFactor); $defectQty = (int) round($productionQty * rand(10, 40) / 1000); // 1~4% 불량률 $defectRate = $productionQty > 0 ? round($defectQty / $productionQty * 100, 2) : 0; $plannedHours = rand(40, 80); $actualHours = $plannedHours + rand(-10, 5); $efficiencyRate = $actualHours > 0 ? round($plannedHours / $actualHours * 100, 2) : 0; $onTime = rand(3, 8); $late = rand(0, 2); $deliveryRate = ($onTime + $late) > 0 ? round($onTime / ($onTime + $late) * 100, 2) : 100; $prodData[] = [ 'tenant_id' => $tenantId, 'stat_date' => $date->toDateString(), 'wo_created_count' => rand(2, 6), 'wo_completed_count' => rand(1, 5), 'wo_in_progress_count' => rand(3, 10), 'wo_overdue_count' => rand(0, 2), 'production_qty' => $productionQty, 'defect_qty' => $defectQty, 'defect_rate' => $defectRate, 'planned_hours' => $plannedHours, 'actual_hours' => $actualHours, 'efficiency_rate' => min($efficiencyRate, 120), 'active_worker_count' => rand(8, 20), 'issue_count' => rand(0, 3), 'on_time_delivery_count' => $onTime, 'late_delivery_count' => $late, 'delivery_rate' => $deliveryRate, 'created_at' => now(), 'updated_at' => now(), ]; } DB::connection($statConnection)->table('stat_production_daily') ->where('tenant_id', $tenantId) ->delete(); foreach (array_chunk($prodData, 30) as $chunk) { DB::connection($statConnection)->table('stat_production_daily')->insert($chunk); } } } }