From bdb7460bfae0d8ce44f0803a5e1b313b2f5552a1 Mon Sep 17 00:00:00 2001 From: kent Date: Tue, 30 Dec 2025 09:43:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=92=88=EB=AA=A9(Item)=20=EB=8D=94?= =?UTF-8?q?=EB=AF=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=8B=9C=EB=8D=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DummyItemSeeder.php 생성: 10,000개 품목 더미 데이터 - FG(완제품) 2,000개, PT(부품) 3,000개 - SM(부자재) 2,000개, RM(원자재) 2,000개, CS(소모품) 1,000개 - 1,000개씩 배치 삽입으로 성능 최적화 - 타입별 속성(attributes) 자동 생성 - DummyDataSeeder에 품목 시더 등록 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- database/seeders/Dummy/DummyItemSeeder.php | 159 +++++++++++++++++++++ database/seeders/DummyDataSeeder.php | 12 +- 2 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 database/seeders/Dummy/DummyItemSeeder.php diff --git a/database/seeders/Dummy/DummyItemSeeder.php b/database/seeders/Dummy/DummyItemSeeder.php new file mode 100644 index 0000000..1fdff69 --- /dev/null +++ b/database/seeders/Dummy/DummyItemSeeder.php @@ -0,0 +1,159 @@ + 2000, // 완제품 + 'PT' => 3000, // 부품 + 'SM' => 2000, // 부자재 + 'RM' => 2000, // 원자재 + 'CS' => 1000, // 소모품 + ]; + + /** + * 타입별 코드 접두사 + */ + private const TYPE_PREFIXES = [ + 'FG' => 'FG', + 'PT' => 'PT', + 'SM' => 'SM', + 'RM' => 'RM', + 'CS' => 'CS', + ]; + + /** + * 타입별 품목명 패턴 + */ + private const TYPE_NAMES = [ + 'FG' => ['스크린', '블라인드', '커튼', '차양막', '롤스크린', '버티칼', '우드블라인드', '허니콤', '듀오롤', '콤비블라인드'], + 'PT' => ['모터', '브라켓', '가이드레일', '샤프트', '베어링', '기어박스', '풀리', '벨트', '스프링', '힌지'], + 'SM' => ['나사', '볼트', '너트', '와셔', '리벳', '클립', '고정핀', '스토퍼', '캡', '부싱'], + 'RM' => ['알루미늄', '스틸', 'PVC', '폴리에스터', '면', '폴리카보네이트', 'ABS', '나일론', '실리콘', '고무'], + 'CS' => ['포장재', '라벨', '테이프', '박스', '비닐', '완충재', '스티커', '인쇄물', '매뉴얼', '보증서'], + ]; + + /** + * 단위 목록 + */ + private const UNITS = ['EA', 'SET', 'M', 'MM', 'KG', 'G', 'L', 'ML', 'BOX', 'ROLL']; + + /** + * 색상 목록 + */ + private const COLORS = ['화이트', '아이보리', '베이지', '그레이', '블랙', '브라운', '네이비', '카키', '버건디', '실버']; + + /** + * 규격 목록 + */ + private const SPECS = ['소형', '중형', '대형', '특대형', 'A타입', 'B타입', 'C타입', '표준', '고급', '프리미엄']; + + public function run(): void + { + $tenantId = DummyDataSeeder::TENANT_ID; + $userId = DummyDataSeeder::USER_ID; + + // 기존 데이터 확인 + $existing = Item::where('tenant_id', $tenantId)->count(); + if ($existing >= 10000) { + $this->command->info(' ⚠ items: 이미 '.$existing.'개 존재 (스킵)'); + + return; + } + + $this->command->info(' 🔄 items: 10,000개 생성 중...'); + + $totalCount = 0; + $now = now(); + + foreach (self::TYPE_COUNTS as $type => $count) { + $prefix = self::TYPE_PREFIXES[$type]; + $names = self::TYPE_NAMES[$type]; + $items = []; + + for ($i = 1; $i <= $count; $i++) { + $code = sprintf('%s-%06d', $prefix, $i); + $baseName = $names[array_rand($names)]; + $color = self::COLORS[array_rand(self::COLORS)]; + $spec = self::SPECS[array_rand(self::SPECS)]; + $name = "{$baseName} {$color} {$spec}"; + + $items[] = [ + 'tenant_id' => $tenantId, + 'item_type' => $type, + 'code' => $code, + 'name' => $name, + 'unit' => self::UNITS[array_rand(self::UNITS)], + 'category_id' => null, + 'bom' => null, + 'attributes' => json_encode($this->generateAttributes($type)), + 'attributes_archive' => null, + 'options' => json_encode(['color' => $color, 'spec' => $spec]), + 'description' => "{$type} 타입 품목 - {$name}", + 'is_active' => rand(1, 100) <= 95, // 95% 활성 + 'created_by' => $userId, + 'updated_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + // 1000개씩 배치 삽입 + if (count($items) >= 1000) { + DB::table('items')->insert($items); + $totalCount += count($items); + $items = []; + $this->command->info(" → {$totalCount}개 완료..."); + } + } + + // 남은 데이터 삽입 + if (! empty($items)) { + DB::table('items')->insert($items); + $totalCount += count($items); + } + } + + $this->command->info(' ✓ items: '.$totalCount.'건 생성 완료'); + } + + /** + * 타입별 속성 생성 + */ + private function generateAttributes(string $type): array + { + $base = [ + 'weight' => rand(10, 10000) / 100, // 0.1 ~ 100 kg + 'width' => rand(10, 3000), // 10 ~ 3000 mm + 'height' => rand(10, 3000), // 10 ~ 3000 mm + ]; + + return match ($type) { + 'FG' => array_merge($base, [ + 'warranty_months' => rand(6, 36), + 'lead_time_days' => rand(3, 30), + ]), + 'PT' => array_merge($base, [ + 'material' => ['알루미늄', '스틸', 'ABS', 'PVC'][array_rand(['알루미늄', '스틸', 'ABS', 'PVC'])], + 'tolerance' => '±'.rand(1, 10) / 10 .'mm', + ]), + 'SM', 'RM' => array_merge($base, [ + 'min_order_qty' => rand(1, 100) * 10, + 'supplier' => ['국내', '중국', '베트남', '일본'][array_rand(['국내', '중국', '베트남', '일본'])], + ]), + 'CS' => [ + 'expiry_months' => rand(6, 24), + 'storage' => ['상온', '냉장', '냉동'][array_rand(['상온', '냉장', '냉동'])], + ], + default => $base, + }; + } +} \ No newline at end of file diff --git a/database/seeders/DummyDataSeeder.php b/database/seeders/DummyDataSeeder.php index 31b5d36..9e55137 100644 --- a/database/seeders/DummyDataSeeder.php +++ b/database/seeders/DummyDataSeeder.php @@ -60,7 +60,14 @@ public function run(): void ApprovalTestDataSeeder::class, ]); - // 5. 기타 데이터 + // 5. 품목 데이터 + $this->command->info(''); + $this->command->info('📦 품목 데이터 생성...'); + $this->call([ + Dummy\DummyItemSeeder::class, + ]); + + // 6. 기타 데이터 $this->command->info(''); $this->command->info('📋 기타 데이터 생성...'); $this->call([ @@ -94,9 +101,10 @@ public function run(): void ['결재', 'approval_forms', '3'], ['결재', 'approvals', '20'], ['결재', 'approval_steps', '~40'], + ['품목', 'items', '10,000'], ['기타', 'popups', '8'], ['기타', 'payments', '13'], - ['', '총계', '~1,010'], + ['', '총계', '~11,010'], ] ); }