option('dry-run'); $this->info('=== 경동 견적 단가 마이그레이션 ==='); $this->info($dryRun ? '[DRY RUN] 실제 변경 없음' : '[LIVE] DB에 반영합니다'); $this->newLine(); DB::beginTransaction(); try { // 1. 레거시 BDmodels (chandj DB) $this->migrateBDModels($dryRun); // 2. kd_price_tables (motor, shaft, pipe, angle, raw_material) $this->migrateKdPriceTables($dryRun); if ($dryRun) { DB::rollBack(); $this->warn('[DRY RUN] 롤백 완료'); } else { DB::commit(); $this->info('커밋 완료'); } $this->newLine(); $this->info("생성: {$this->created}건, 스킵: {$this->skipped}건"); return Command::SUCCESS; } catch (\Exception $e) { DB::rollBack(); $this->error("오류: {$e->getMessage()}"); return Command::FAILURE; } } /** * 레거시 chandj.BDmodels → items + item_details + prices */ private function migrateBDModels(bool $dryRun): void { $this->info('--- BDmodels (레거시) ---'); // chandj DB에서 BDmodels 조회 (chandj connection 사용) $rows = DB::connection('chandj')->select(" SELECT model_name, seconditem, finishing_type, spec, unitprice, description FROM BDmodels WHERE is_deleted = 0 ORDER BY model_name, seconditem, finishing_type, spec "); foreach ($rows as $row) { $modelName = trim($row->model_name ?? ''); $secondItem = trim($row->seconditem ?? ''); $finishingType = trim($row->finishing_type ?? ''); $spec = trim($row->spec ?? ''); $unitPrice = (float) str_replace(',', '', $row->unitprice ?? '0'); // finishing_type 정규화: 'SUS마감' → 'SUS', 'EGI마감' → 'EGI' $finishingType = str_replace('마감', '', $finishingType); if (empty($secondItem) || $unitPrice <= 0) { $this->skipped++; continue; } // 코드 생성 $codeParts = ['BD', $secondItem]; if ($modelName) { $codeParts[] = $modelName; } if ($finishingType) { $codeParts[] = $finishingType; } if ($spec) { $codeParts[] = $spec; } $code = implode('-', $codeParts); // 이름 생성 $nameParts = [$secondItem]; if ($modelName) { $nameParts[] = $modelName; } if ($finishingType) { $nameParts[] = $finishingType; } if ($spec) { $nameParts[] = $spec; } $name = implode(' ', $nameParts); $this->createEstimateItem( code: $code, name: $name, productCategory: 'bdmodels', partType: $secondItem, specification: $spec ?: null, attributes: array_filter([ 'model_name' => $modelName ?: null, 'finishing_type' => $finishingType ?: null, 'bdmodel_source' => 'BDmodels', 'description' => $row->description ?: null, ]), salesPrice: $unitPrice, note: 'BDmodels 마이그레이션', dryRun: $dryRun ); } } /** * kd_price_tables → items + item_details + prices */ private function migrateKdPriceTables(bool $dryRun): void { $this->info('--- kd_price_tables ---'); $rows = DB::table('kd_price_tables') ->where('tenant_id', self::TENANT_ID) ->where('is_active', true) ->where('table_type', '!=', 'bdmodels') // BDmodels는 위에서 처리 ->orderBy('table_type') ->orderBy('item_code') ->get(); foreach ($rows as $row) { $tableType = $row->table_type; $unitPrice = (float) $row->unit_price; if ($unitPrice <= 0) { $this->skipped++; continue; } switch ($tableType) { case 'motor': $this->migrateMotor($row, $dryRun); break; case 'shaft': $this->migrateShaft($row, $dryRun); break; case 'pipe': $this->migratePipe($row, $dryRun); break; case 'angle': $this->migrateAngle($row, $dryRun); break; case 'raw_material': $this->migrateRawMaterial($row, $dryRun); break; } } } private function migrateMotor(object $row, bool $dryRun): void { $category = $row->category; // 150K, 300K, 매립형, 노출형 등 $code = "EST-MOTOR-{$category}"; $name = "모터/제어기 {$category}"; $this->createEstimateItem( code: $code, name: $name, productCategory: 'motor', partType: $category, specification: $row->spec2 ?? null, attributes: ['price_unit' => $row->unit ?? 'EA'], salesPrice: (float) $row->unit_price, note: 'kd_price_tables motor 마이그레이션', dryRun: $dryRun ); } private function migrateShaft(object $row, bool $dryRun): void { $size = $row->spec1; // 인치 $length = $row->spec2; // 길이 $code = "EST-SHAFT-{$size}-{$length}"; $name = "감기샤프트 {$size}인치 {$length}m"; $this->createEstimateItem( code: $code, name: $name, productCategory: 'shaft', partType: $size, specification: $length, attributes: ['price_unit' => $row->unit ?? 'EA'], salesPrice: (float) $row->unit_price, note: 'kd_price_tables shaft 마이그레이션', dryRun: $dryRun ); } private function migratePipe(object $row, bool $dryRun): void { $thickness = $row->spec1; $length = $row->spec2; $code = "EST-PIPE-{$thickness}-{$length}"; $name = "각파이프 {$thickness}T {$length}mm"; $this->createEstimateItem( code: $code, name: $name, productCategory: 'pipe', partType: $thickness, specification: $length, attributes: ['price_unit' => $row->unit ?? 'EA'], salesPrice: (float) $row->unit_price, note: 'kd_price_tables pipe 마이그레이션', dryRun: $dryRun ); } private function migrateAngle(object $row, bool $dryRun): void { $category = $row->category; // 스크린용, 철재용 $bracketSize = $row->spec1; // 530*320, 600*350, 690*390 $angleType = $row->spec2; // 앵글3T, 앵글4T $code = "EST-ANGLE-{$category}-{$bracketSize}-{$angleType}"; $name = "앵글 {$category} {$bracketSize} {$angleType}"; $this->createEstimateItem( code: $code, name: $name, productCategory: 'angle', partType: $category, specification: $bracketSize, attributes: [ 'angle_type' => $angleType, 'price_unit' => $row->unit ?? 'EA', ], salesPrice: (float) $row->unit_price, note: 'kd_price_tables angle 마이그레이션', dryRun: $dryRun ); } private function migrateRawMaterial(object $row, bool $dryRun): void { $name = $row->item_name; $code = 'EST-RAW-'.preg_replace('/[^A-Za-z0-9가-힣]/', '', $name); $this->createEstimateItem( code: $code, name: $name, productCategory: 'raw_material', partType: $name, specification: $row->spec1 ?? null, attributes: ['price_unit' => $row->unit ?? 'EA'], salesPrice: (float) $row->unit_price, note: 'kd_price_tables raw_material 마이그레이션', dryRun: $dryRun ); } /** * 견적 품목 생성 (items + item_details + prices) */ private function createEstimateItem( string $code, string $name, string $productCategory, string $partType, ?string $specification, array $attributes, float $salesPrice, string $note, bool $dryRun ): void { // 중복 체크 (code 기준) $existing = DB::table('items') ->where('tenant_id', self::TENANT_ID) ->where('code', $code) ->whereNull('deleted_at') ->first(); if ($existing) { $this->line(" [스킵] {$code} - 이미 존재"); $this->skipped++; return; } $this->line(" [생성] {$code} ({$name}) = {$salesPrice}"); if ($dryRun) { $this->created++; return; } $now = now(); // 1. items $itemId = DB::table('items')->insertGetId([ 'tenant_id' => self::TENANT_ID, 'item_type' => 'PT', 'code' => $code, 'name' => $name, 'unit' => 'EA', 'attributes' => json_encode($attributes, JSON_UNESCAPED_UNICODE), 'is_active' => true, 'created_at' => $now, 'updated_at' => $now, ]); // 2. item_details DB::table('item_details')->insert([ 'item_id' => $itemId, 'product_category' => $productCategory, 'part_type' => $partType, 'specification' => $specification, 'item_name' => $name, 'is_purchasable' => true, 'created_at' => $now, 'updated_at' => $now, ]); // 3. prices DB::table('prices')->insert([ 'tenant_id' => self::TENANT_ID, 'item_type_code' => 'PT', 'item_id' => $itemId, 'sales_price' => $salesPrice, 'effective_from' => $now->toDateString(), 'status' => 'active', 'note' => $note, 'created_at' => $now, 'updated_at' => $now, ]); $this->created++; } }