diff --git a/database/seeders/Kyungdong/KyungdongItemSeeder.php b/database/seeders/Kyungdong/KyungdongItemSeeder.php index bce7a85..3c0f707 100644 --- a/database/seeders/Kyungdong/KyungdongItemSeeder.php +++ b/database/seeders/Kyungdong/KyungdongItemSeeder.php @@ -14,6 +14,8 @@ * Phase 1.2: chandj.item_list (9건) → items (PT), prices * Phase 2.1: chandj.BDmodels.seconditem → items (PT) 누락 부품 추가 * Phase 2.2: chandj.BDmodels → items.bom JSON (FG ↔ PT 연결) + * Phase 3.1: chandj.price_motor → items (SM) + prices 누락 품목 + * Phase 3.2: chandj.price_raw_materials → items (RM) + prices 누락 품목 * * @see docs/plans/kd-items-migration-plan.md */ @@ -71,11 +73,25 @@ public function run(): void // Phase 2.2: BDmodels → items.bom JSON $bomCount = $this->migrateBom($tenantId); - $totalItems = $itemCount + $modelCount + $itemListCount + $bdPartsCount; + // Phase 3.1: price_motor → items (SM) + prices + $motorResult = $this->migratePriceMotor($tenantId, $userId); + + // Phase 3.2: price_raw_materials → items (RM) + prices + $rawMatResult = $this->migratePriceRawMaterials($tenantId, $userId); + + $totalItems = $itemCount + $modelCount + $itemListCount + $bdPartsCount + $motorResult['items'] + $rawMatResult['items']; + $totalPrices = $priceCount + $motorResult['prices'] + $rawMatResult['prices']; + $this->command->info(''); $this->command->info('✅ 마이그레이션 완료:'); - $this->command->info(" → items: {$totalItems}건 (KDunitprice {$itemCount} + models {$modelCount} + item_list {$itemListCount} + BDmodels부품 {$bdPartsCount})"); - $this->command->info(" → prices: {$priceCount}건"); + $this->command->info(" → items: {$totalItems}건"); + $this->command->info(" - KDunitprice: {$itemCount}건"); + $this->command->info(" - models: {$modelCount}건"); + $this->command->info(" - item_list: {$itemListCount}건"); + $this->command->info(" - BDmodels부품: {$bdPartsCount}건"); + $this->command->info(" - price_motor: {$motorResult['items']}건"); + $this->command->info(" - price_raw_materials: {$rawMatResult['items']}건"); + $this->command->info(" → prices: {$totalPrices}건"); $this->command->info(" → BOM 연결: {$bomCount}건"); } @@ -580,4 +596,316 @@ private function migrateBom(int $tenantId): int return $updatedCount; } + + /** + * Phase 3.1: price_motor → items (SM) + prices + * + * price_motor JSON에서 누락된 품목만 추가 + * - 제어기, 방화/방범 콘트롤박스, 스위치, 리모콘 등 + * + * @return array{items: int, prices: int} + */ + private function migratePriceMotor(int $tenantId, int $userId): array + { + $this->command->info(''); + $this->command->info('📦 [Phase 3.1] price_motor → items (SM) 누락 품목...'); + + // 최신 price_motor 데이터 조회 + $priceMotor = DB::connection('chandj') + ->table('price_motor') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceMotor || empty($priceMotor->itemList)) { + $this->command->info(' → 소스 데이터 없음'); + + return ['items' => 0, 'prices' => 0]; + } + + $itemList = json_decode($priceMotor->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return ['items' => 0, 'prices' => 0]; + } + + // 기존 items 이름 조회 (중복 체크용) + $existingNames = DB::table('items') + ->where('tenant_id', $tenantId) + ->pluck('name') + ->map(fn ($n) => mb_strtolower($n)) + ->toArray(); + + $items = []; + $now = now(); + $newItemCodes = []; + + foreach ($itemList as $idx => $item) { + $col1 = $item['col1'] ?? ''; // 전압/카테고리 (220, 380, 제어기, 방화, 방범) + $col2 = $item['col2'] ?? ''; // 용량/품목명 + $salesPrice = (float) str_replace(',', '', $item['col13'] ?? '0'); + + // 모터 품목은 KDunitprice에 이미 있으므로 스킵 + if (in_array($col1, ['220', '380'])) { + continue; + } + + // 품목명 생성 + $name = trim("{$col1} {$col2}"); + if (empty($name) || $name === ' ') { + continue; + } + + // 이미 존재하는 품목 스킵 (유사 이름 체크) + $nameLower = mb_strtolower($name); + $exists = false; + foreach ($existingNames as $existingName) { + if (str_contains($existingName, $nameLower) || str_contains($nameLower, $existingName)) { + $exists = true; + break; + } + } + if ($exists) { + continue; + } + + // 코드 생성 + $code = 'PM-'.str_pad($idx + 1, 3, '0', STR_PAD_LEFT); + + $items[] = [ + 'tenant_id' => $tenantId, + 'item_type' => 'SM', + 'code' => $code, + 'name' => $name, + 'unit' => 'EA', + 'category_id' => null, + 'process_type' => null, + 'item_category' => null, + 'bom' => null, + 'attributes' => json_encode([ + 'price_category' => $col1, + 'price_spec' => $col2, + 'legacy_source' => 'price_motor', + ]), + 'attributes_archive' => null, + 'options' => null, + 'description' => null, + 'is_active' => true, + 'created_by' => $userId, + 'updated_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + $newItemCodes[$code] = $salesPrice; + $existingNames[] = $nameLower; // 중복 방지 + } + + if (! empty($items)) { + DB::table('items')->insert($items); + } + + // prices 생성 + $priceCount = 0; + if (! empty($newItemCodes)) { + $newItems = DB::table('items') + ->where('tenant_id', $tenantId) + ->whereIn('code', array_keys($newItemCodes)) + ->get(['id', 'code', 'item_type']); + + $prices = []; + foreach ($newItems as $item) { + $prices[] = [ + 'tenant_id' => $tenantId, + 'item_type_code' => $item->item_type, + 'item_id' => $item->id, + 'client_group_id' => null, + 'purchase_price' => 0, + 'processing_cost' => null, + 'loss_rate' => null, + 'margin_rate' => null, + 'sales_price' => $newItemCodes[$item->code], + 'rounding_rule' => 'round', + 'rounding_unit' => 1, + 'supplier' => null, + 'effective_from' => $priceMotor->registedate ?? now()->toDateString(), + 'effective_to' => null, + 'note' => 'price_motor 마이그레이션', + 'status' => 'active', + 'is_final' => false, + 'created_by' => $userId, + 'updated_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + } + + if (! empty($prices)) { + DB::table('prices')->insert($prices); + $priceCount = count($prices); + } + } + + $this->command->info(' → 소스 데이터: '.count($itemList).'건 (누락 '.count($items).'건 추가)'); + $this->command->info(' ✓ items: '.count($items).'건, prices: '.$priceCount.'건 생성 완료'); + + return ['items' => count($items), 'prices' => $priceCount]; + } + + /** + * Phase 3.2: price_raw_materials → items (RM) + prices + * + * price_raw_materials JSON에서 누락된 원자재 품목 추가 + * + * @return array{items: int, prices: int} + */ + private function migratePriceRawMaterials(int $tenantId, int $userId): array + { + $this->command->info(''); + $this->command->info('📦 [Phase 3.2] price_raw_materials → items (RM) 누락 품목...'); + + // 최신 price_raw_materials 데이터 조회 + $priceRaw = DB::connection('chandj') + ->table('price_raw_materials') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceRaw || empty($priceRaw->itemList)) { + $this->command->info(' → 소스 데이터 없음'); + + return ['items' => 0, 'prices' => 0]; + } + + $itemList = json_decode($priceRaw->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return ['items' => 0, 'prices' => 0]; + } + + // 기존 items 이름 조회 (중복 체크용) + $existingNames = DB::table('items') + ->where('tenant_id', $tenantId) + ->pluck('name') + ->map(fn ($n) => mb_strtolower($n)) + ->toArray(); + + $items = []; + $now = now(); + $newItemCodes = []; + + foreach ($itemList as $idx => $item) { + $col1 = $item['col1'] ?? ''; // 카테고리 (슬랫, 스크린) + $col2 = $item['col2'] ?? ''; // 품목명 (방화, 실리카, 화이바) + $salesPrice = (float) str_replace(',', '', $item['col13'] ?? '0'); + + // 품목명 생성 + $name = trim("{$col1} {$col2}"); + if (empty($name) || $name === ' ') { + continue; + } + + // 이미 존재하는 품목 스킵 + $nameLower = mb_strtolower($name); + $exists = false; + foreach ($existingNames as $existingName) { + // 정확히 일치하거나 유사한 이름 체크 + $col2Lower = mb_strtolower($col2); + if (str_contains($existingName, $col2Lower) || $existingName === $nameLower) { + $exists = true; + break; + } + } + if ($exists) { + continue; + } + + // 코드 생성 + $code = 'RM-'.str_pad($idx + 1, 3, '0', STR_PAD_LEFT); + + $items[] = [ + 'tenant_id' => $tenantId, + 'item_type' => 'RM', + 'code' => $code, + 'name' => $name, + 'unit' => 'EA', + 'category_id' => null, + 'process_type' => null, + 'item_category' => $col1, + 'bom' => null, + 'attributes' => json_encode([ + 'raw_category' => $col1, + 'raw_name' => $col2, + 'legacy_source' => 'price_raw_materials', + ]), + 'attributes_archive' => null, + 'options' => null, + 'description' => null, + 'is_active' => true, + 'created_by' => $userId, + 'updated_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + $newItemCodes[$code] = $salesPrice; + $existingNames[] = $nameLower; + } + + if (! empty($items)) { + DB::table('items')->insert($items); + } + + // prices 생성 + $priceCount = 0; + if (! empty($newItemCodes)) { + $newItems = DB::table('items') + ->where('tenant_id', $tenantId) + ->whereIn('code', array_keys($newItemCodes)) + ->get(['id', 'code', 'item_type']); + + $prices = []; + foreach ($newItems as $item) { + $prices[] = [ + 'tenant_id' => $tenantId, + 'item_type_code' => $item->item_type, + 'item_id' => $item->id, + 'client_group_id' => null, + 'purchase_price' => 0, + 'processing_cost' => null, + 'loss_rate' => null, + 'margin_rate' => null, + 'sales_price' => $newItemCodes[$item->code], + 'rounding_rule' => 'round', + 'rounding_unit' => 1, + 'supplier' => null, + 'effective_from' => $priceRaw->registedate ?? now()->toDateString(), + 'effective_to' => null, + 'note' => 'price_raw_materials 마이그레이션', + 'status' => 'active', + 'is_final' => false, + 'created_by' => $userId, + 'updated_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + } + + if (! empty($prices)) { + DB::table('prices')->insert($prices); + $priceCount = count($prices); + } + } + + $this->command->info(' → 소스 데이터: '.count($itemList).'건 (누락 '.count($items).'건 추가)'); + $this->command->info(' ✓ items: '.count($items).'건, prices: '.$priceCount.'건 생성 완료'); + + return ['items' => count($items), 'prices' => $priceCount]; + } }