option('tenant_id'); $execute = $this->option('execute'); $dryRun = ! $execute; $this->info('=== 품목 치수 정규화 ==='); $this->info("Tenant ID: {$tenantId}"); $this->info('Mode: '.($dryRun ? 'DRY-RUN (미리보기)' : 'EXECUTE (실행)')); $this->newLine(); $items = DB::table('items') ->where('tenant_id', $tenantId) ->whereNull('deleted_at') ->whereRaw("JSON_EXTRACT(attributes, '$.\"101_specification_1\"') IS NOT NULL") ->get(); $this->info("대상 품목: {$items->count()}건 (101_specification_1 존재)"); $this->newLine(); $bar = $this->output->createProgressBar($items->count()); $bar->start(); foreach ($items as $item) { $this->processItem($item, $dryRun); $bar->advance(); } $bar->finish(); $this->newLine(2); $this->showResults($dryRun); return self::SUCCESS; } private function processItem(object $item, bool $dryRun): void { $attributes = json_decode($item->attributes, true) ?? []; $spec1 = $attributes['101_specification_1'] ?? null; $spec2 = $attributes['102_specification_2'] ?? null; $spec3 = $attributes['103_specification_3'] ?? null; $existingThickness = $attributes['thickness'] ?? null; $existingWidth = $attributes['width'] ?? null; $existingLength = $attributes['length'] ?? null; $changed = false; $changeDetails = []; // thickness 추출: spec1에서 숫자 추출 (t/T 제거) if ($spec1 !== null && $spec1 !== '' && $existingThickness === null) { $thickness = $this->extractThickness($spec1); if ($thickness !== null) { $attributes['thickness'] = $thickness; $changeDetails[] = "thickness: {$spec1} → {$thickness}"; $changed = true; } } // width 추출: spec2에서 순수 숫자만 if ($spec2 !== null && $spec2 !== '' && $existingWidth === null) { $width = $this->extractNumeric($spec2); if ($width !== null) { $attributes['width'] = $width; $changeDetails[] = "width: {$spec2} → {$width}"; $changed = true; } } // length 추출: spec3에서 순수 숫자만 (c, P/L, 문자 포함 시 스킵) if ($spec3 !== null && $spec3 !== '' && $existingLength === null) { $length = $this->extractLength($spec3); if ($length !== null) { $attributes['length'] = $length; $changeDetails[] = "length: {$spec3} → {$length}"; $changed = true; } } if ($changed) { $this->changes[] = [ 'id' => $item->id, 'name' => $item->name, 'changes' => implode(', ', $changeDetails), ]; if (! $dryRun) { DB::table('items') ->where('id', $item->id) ->update(['attributes' => json_encode($attributes, JSON_UNESCAPED_UNICODE)]); } $this->updatedCount++; } else { $this->skippedCount++; } } /** * thickness 추출: "t1.2", "T1.2", "1.2", "egi1.17" → 숫자 * 패턴: 선행 문자(t/T/영문) 제거 후 숫자 추출 */ private function extractThickness(?string $value): ?string { if ($value === null || trim($value) === '') { return null; } $cleaned = trim($value); // "t1.2", "T1.2" → "1.2" $cleaned = preg_replace('/^[tT]/', '', $cleaned); // "egi1.17", "sus1.2" → 영문자 제거 후 숫자 추출 if (preg_match('/(\d+(?:\.\d+)?)/', $cleaned, $matches)) { return $matches[1]; } return null; } /** * 순수 숫자만 추출 (정수/소수) * "1219" → "1219", "1219.5" → "1219.5" * "c" → null, "" → null, "P/L" → null */ private function extractNumeric(?string $value): ?string { if ($value === null || trim($value) === '') { return null; } $cleaned = trim($value); // 순수 숫자 (정수/소수)만 허용 if (preg_match('/^\d+(?:\.\d+)?$/', $cleaned)) { return $cleaned; } return null; } /** * length 추출: "3000" → "3000", "3000 P/L" → "3000", "c" → null * 선행 숫자가 있고 뒤에 공백+문자(P/L 등)가 붙는 경우 숫자만 추출 */ private function extractLength(?string $value): ?string { if ($value === null || trim($value) === '') { return null; } $cleaned = trim($value); // 순수 숫자 if (preg_match('/^\d+(?:\.\d+)?$/', $cleaned)) { return $cleaned; } // "3000 P/L" → "3000" (숫자로 시작하고 뒤에 공백+문자) if (preg_match('/^(\d+(?:\.\d+)?)\s+/', $cleaned, $matches)) { return $matches[1]; } // "c", "P/L" 등 숫자 없는 경우 return null; } private function showResults(bool $dryRun): void { $this->info('=== 결과 ==='); $this->info("변경 대상: {$this->updatedCount}건"); $this->info("스킵 (변경 불필요): {$this->skippedCount}건"); $this->newLine(); if (! empty($this->changes)) { $this->table( ['ID', '품목명', '변경 내용'], array_map(fn ($c) => [$c['id'], mb_substr($c['name'], 0, 30), $c['changes']], $this->changes) ); } if ($dryRun) { $this->newLine(); $this->warn('DRY-RUN 모드입니다. 실제 적용하려면 --execute 옵션을 사용하세요:'); $this->line(' php artisan items:normalize-dimensions --execute'); } else { $this->newLine(); $this->info("총 {$this->updatedCount}건 업데이트 완료"); } } }