583 lines
22 KiB
PHP
583 lines
22 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use Illuminate\Console\Attributes\AsCommand;
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 5130 가격표 품목 → SAM items 마이그레이션
|
||
|
|
*
|
||
|
|
* 대상 테이블:
|
||
|
|
* - KDunitprice → items (SM, RM, CS)
|
||
|
|
* - price_raw_materials → items (RM)
|
||
|
|
* - price_bend → items (PT)
|
||
|
|
*/
|
||
|
|
#[AsCommand(name: 'migrate:5130-price-items', description: '5130 가격표(KDunitprice, price_raw_materials, price_bend) → SAM items 마이그레이션')]
|
||
|
|
class Migrate5130PriceItems extends Command
|
||
|
|
{
|
||
|
|
protected $signature = 'migrate:5130-price-items
|
||
|
|
{--tenant_id=287 : Target tenant ID (default: 287 경동기업)}
|
||
|
|
{--dry-run : 실제 저장 없이 시뮬레이션만 수행}
|
||
|
|
{--step=all : 실행할 단계 (all|kdunitprice|raw_materials|bend)}
|
||
|
|
{--rollback : 마이그레이션 롤백}
|
||
|
|
{--limit=0 : 테스트용 레코드 수 제한 (0=전체)}';
|
||
|
|
|
||
|
|
// 5130 DB 연결 (chandj)
|
||
|
|
private string $sourceDb = 'chandj';
|
||
|
|
|
||
|
|
// SAM DB 연결
|
||
|
|
private string $targetDb = 'mysql';
|
||
|
|
|
||
|
|
// 통계
|
||
|
|
private array $stats = [
|
||
|
|
'kdunitprice' => ['total' => 0, 'migrated' => 0, 'skipped' => 0],
|
||
|
|
'raw_materials' => ['total' => 0, 'migrated' => 0, 'skipped' => 0],
|
||
|
|
'bend' => ['total' => 0, 'migrated' => 0, 'skipped' => 0],
|
||
|
|
];
|
||
|
|
|
||
|
|
public function handle(): int
|
||
|
|
{
|
||
|
|
$tenantId = (int) $this->option('tenant_id');
|
||
|
|
$dryRun = $this->option('dry-run');
|
||
|
|
$step = $this->option('step');
|
||
|
|
$rollback = $this->option('rollback');
|
||
|
|
$limit = (int) $this->option('limit');
|
||
|
|
|
||
|
|
$this->info('╔══════════════════════════════════════════════════════════════╗');
|
||
|
|
$this->info('║ 5130 가격표 → SAM items 마이그레이션 ║');
|
||
|
|
$this->info('╚══════════════════════════════════════════════════════════════╝');
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("📌 Tenant ID: {$tenantId}");
|
||
|
|
$this->info('📌 Mode: '.($dryRun ? '🔍 DRY-RUN (시뮬레이션)' : '⚡ LIVE'));
|
||
|
|
$this->info("📌 Step: {$step}");
|
||
|
|
if ($limit > 0) {
|
||
|
|
$this->warn("📌 Limit: {$limit} records (테스트 모드)");
|
||
|
|
}
|
||
|
|
$this->newLine();
|
||
|
|
|
||
|
|
if ($rollback) {
|
||
|
|
return $this->rollbackMigration($tenantId, $dryRun);
|
||
|
|
}
|
||
|
|
|
||
|
|
$steps = $step === 'all'
|
||
|
|
? ['kdunitprice', 'raw_materials', 'bend']
|
||
|
|
: [$step];
|
||
|
|
|
||
|
|
foreach ($steps as $currentStep) {
|
||
|
|
$this->line('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
|
|
$this->info(">>> Step: {$currentStep}");
|
||
|
|
$this->newLine();
|
||
|
|
|
||
|
|
match ($currentStep) {
|
||
|
|
'kdunitprice' => $this->migrateKDunitprice($tenantId, $dryRun, $limit),
|
||
|
|
'raw_materials' => $this->migratePriceRawMaterials($tenantId, $dryRun, $limit),
|
||
|
|
'bend' => $this->migratePriceBend($tenantId, $dryRun, $limit),
|
||
|
|
default => $this->error("Unknown step: {$currentStep}"),
|
||
|
|
};
|
||
|
|
|
||
|
|
$this->newLine();
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->showSummary();
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* KDunitprice → items (SM, RM, CS)
|
||
|
|
*/
|
||
|
|
private function migrateKDunitprice(int $tenantId, bool $dryRun, int $limit): void
|
||
|
|
{
|
||
|
|
$this->info('📦 Migrating KDunitprice → items (SM, RM, CS)...');
|
||
|
|
|
||
|
|
$query = DB::connection($this->sourceDb)->table('KDunitprice')
|
||
|
|
->whereNull('is_deleted')
|
||
|
|
->orWhere('is_deleted', 0);
|
||
|
|
|
||
|
|
if ($limit > 0) {
|
||
|
|
$query->limit($limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
$items = $query->get();
|
||
|
|
$this->stats['kdunitprice']['total'] = $items->count();
|
||
|
|
$this->line("Found {$items->count()} records");
|
||
|
|
|
||
|
|
$bar = $this->output->createProgressBar($items->count());
|
||
|
|
$bar->start();
|
||
|
|
|
||
|
|
foreach ($items as $item) {
|
||
|
|
// 이미 존재하는지 확인 (code 기준)
|
||
|
|
$exists = DB::connection($this->targetDb)->table('items')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('code', $item->prodcode)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
$this->stats['kdunitprice']['skipped']++;
|
||
|
|
$bar->advance();
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// item_type 결정
|
||
|
|
$itemType = $this->determineItemType($item->item_div);
|
||
|
|
|
||
|
|
// 단가 파싱 (콤마 제거)
|
||
|
|
$unitPrice = $this->parseNumber($item->unitprice);
|
||
|
|
|
||
|
|
$itemData = [
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'item_type' => $itemType,
|
||
|
|
'item_category' => $item->item_div,
|
||
|
|
'code' => $item->prodcode,
|
||
|
|
'name' => $item->item_name ?: '(이름없음)',
|
||
|
|
'unit' => $item->unit ?: 'EA',
|
||
|
|
'attributes' => json_encode([
|
||
|
|
'spec' => $item->spec,
|
||
|
|
'unit_price' => $unitPrice,
|
||
|
|
'update_log' => $item->update_log,
|
||
|
|
'source' => '5130',
|
||
|
|
'source_table' => 'KDunitprice',
|
||
|
|
'source_id' => $item->num,
|
||
|
|
], JSON_UNESCAPED_UNICODE),
|
||
|
|
'is_active' => true,
|
||
|
|
'created_at' => now(),
|
||
|
|
'updated_at' => now(),
|
||
|
|
];
|
||
|
|
|
||
|
|
if (! $dryRun) {
|
||
|
|
DB::connection($this->targetDb)->table('items')->insert($itemData);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->stats['kdunitprice']['migrated']++;
|
||
|
|
$bar->advance();
|
||
|
|
}
|
||
|
|
|
||
|
|
$bar->finish();
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("✅ KDunitprice 완료: {$this->stats['kdunitprice']['migrated']} migrated, {$this->stats['kdunitprice']['skipped']} skipped");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* price_raw_materials → items (RM)
|
||
|
|
* 최신 버전(registedate)만 마이그레이션
|
||
|
|
*/
|
||
|
|
private function migratePriceRawMaterials(int $tenantId, bool $dryRun, int $limit): void
|
||
|
|
{
|
||
|
|
$this->info('📦 Migrating price_raw_materials → items (RM)...');
|
||
|
|
|
||
|
|
// 최신 registedate 조회
|
||
|
|
$latestRecord = DB::connection($this->sourceDb)->table('price_raw_materials')
|
||
|
|
->whereNull('is_deleted')
|
||
|
|
->orWhere('is_deleted', 0)
|
||
|
|
->orderBy('registedate', 'desc')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if (! $latestRecord) {
|
||
|
|
$this->warn('No records found in price_raw_materials');
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->line("Latest version: {$latestRecord->registedate}");
|
||
|
|
|
||
|
|
$itemList = json_decode($latestRecord->itemList ?? '[]', true);
|
||
|
|
if (empty($itemList)) {
|
||
|
|
$this->warn('Empty itemList in latest record');
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$totalItems = count($itemList);
|
||
|
|
if ($limit > 0 && $limit < $totalItems) {
|
||
|
|
$itemList = array_slice($itemList, 0, $limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->stats['raw_materials']['total'] = count($itemList);
|
||
|
|
$this->line('Found '.count($itemList).' items in itemList');
|
||
|
|
|
||
|
|
$bar = $this->output->createProgressBar(count($itemList));
|
||
|
|
$bar->start();
|
||
|
|
|
||
|
|
$orderNo = 0;
|
||
|
|
foreach ($itemList as $row) {
|
||
|
|
$orderNo++;
|
||
|
|
|
||
|
|
// 코드 생성: col14가 있으면 사용, 없으면 자동생성
|
||
|
|
$code = ! empty($row['col14'])
|
||
|
|
? $row['col14']
|
||
|
|
: $this->generateCode('RM', $latestRecord->registedate, $orderNo);
|
||
|
|
|
||
|
|
// 이미 존재하는지 확인
|
||
|
|
$exists = DB::connection($this->targetDb)->table('items')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('code', $code)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
$this->stats['raw_materials']['skipped']++;
|
||
|
|
$bar->advance();
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 품목명 생성
|
||
|
|
$name = trim(($row['col1'] ?? '').' '.($row['col2'] ?? '')) ?: '(이름없음)';
|
||
|
|
|
||
|
|
// item_category 결정
|
||
|
|
$itemCategory = $this->determineRawMaterialCategory($row['col1'] ?? '');
|
||
|
|
|
||
|
|
$itemData = [
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'item_type' => 'RM',
|
||
|
|
'item_category' => $itemCategory,
|
||
|
|
'code' => $code,
|
||
|
|
'name' => $name,
|
||
|
|
'unit' => 'kg',
|
||
|
|
'attributes' => json_encode([
|
||
|
|
'product_type' => $row['col1'] ?? null,
|
||
|
|
'sub_type' => $row['col2'] ?? null,
|
||
|
|
'length' => $this->parseNumber($row['col3'] ?? null),
|
||
|
|
'thickness' => $this->parseNumber($row['col4'] ?? null),
|
||
|
|
'specific_gravity' => $this->parseNumber($row['col5'] ?? null),
|
||
|
|
'area_per_unit' => $this->parseNumber($row['col6'] ?? null),
|
||
|
|
'weight_per_sqm' => $this->parseNumber($row['col7'] ?? null),
|
||
|
|
'purchase_price_per_kg' => $this->parseNumber($row['col8'] ?? null),
|
||
|
|
'price_per_sqm_with_loss' => $this->parseNumber($row['col9'] ?? null),
|
||
|
|
'processing_cost' => $this->parseNumber($row['col10'] ?? null),
|
||
|
|
'mimi' => $this->parseNumber($row['col11'] ?? null),
|
||
|
|
'total' => $this->parseNumber($row['col12'] ?? null),
|
||
|
|
'total_with_labor' => $this->parseNumber($row['col13'] ?? null),
|
||
|
|
'non_certified_code' => $row['col14'] ?? null,
|
||
|
|
'non_certified_price' => $this->parseNumber($row['col15'] ?? null),
|
||
|
|
'source' => '5130',
|
||
|
|
'source_table' => 'price_raw_materials',
|
||
|
|
'source_id' => $latestRecord->num,
|
||
|
|
'source_version' => $latestRecord->registedate,
|
||
|
|
'source_order' => $orderNo,
|
||
|
|
], JSON_UNESCAPED_UNICODE),
|
||
|
|
'is_active' => true,
|
||
|
|
'created_at' => now(),
|
||
|
|
'updated_at' => now(),
|
||
|
|
];
|
||
|
|
|
||
|
|
if (! $dryRun) {
|
||
|
|
DB::connection($this->targetDb)->table('items')->insert($itemData);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->stats['raw_materials']['migrated']++;
|
||
|
|
$bar->advance();
|
||
|
|
}
|
||
|
|
|
||
|
|
$bar->finish();
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("✅ price_raw_materials 완료: {$this->stats['raw_materials']['migrated']} migrated, {$this->stats['raw_materials']['skipped']} skipped");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* price_bend → items (PT)
|
||
|
|
* 최신 버전(registedate)만 마이그레이션
|
||
|
|
*/
|
||
|
|
private function migratePriceBend(int $tenantId, bool $dryRun, int $limit): void
|
||
|
|
{
|
||
|
|
$this->info('📦 Migrating price_bend → items (PT)...');
|
||
|
|
|
||
|
|
// 최신 registedate 조회
|
||
|
|
$latestRecord = DB::connection($this->sourceDb)->table('price_bend')
|
||
|
|
->whereNull('is_deleted')
|
||
|
|
->orWhere('is_deleted', 0)
|
||
|
|
->orderBy('registedate', 'desc')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if (! $latestRecord) {
|
||
|
|
$this->warn('No records found in price_bend');
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->line("Latest version: {$latestRecord->registedate}");
|
||
|
|
|
||
|
|
$itemList = json_decode($latestRecord->itemList ?? '[]', true);
|
||
|
|
if (empty($itemList)) {
|
||
|
|
$this->warn('Empty itemList in latest record');
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$totalItems = count($itemList);
|
||
|
|
if ($limit > 0 && $limit < $totalItems) {
|
||
|
|
$itemList = array_slice($itemList, 0, $limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->stats['bend']['total'] = count($itemList);
|
||
|
|
$this->line('Found '.count($itemList).' items in itemList');
|
||
|
|
|
||
|
|
$bar = $this->output->createProgressBar(count($itemList));
|
||
|
|
$bar->start();
|
||
|
|
|
||
|
|
$orderNo = 0;
|
||
|
|
foreach ($itemList as $row) {
|
||
|
|
$orderNo++;
|
||
|
|
|
||
|
|
// 코드 자동생성
|
||
|
|
$code = $this->generateCode('PT', $latestRecord->registedate, $orderNo);
|
||
|
|
|
||
|
|
// 이미 존재하는지 확인
|
||
|
|
$exists = DB::connection($this->targetDb)->table('items')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('code', $code)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
$this->stats['bend']['skipped']++;
|
||
|
|
$bar->advance();
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 품목명 생성
|
||
|
|
$name = trim(($row['col1'] ?? '').' '.($row['col2'] ?? '')) ?: '(이름없음)';
|
||
|
|
|
||
|
|
// item_category 결정 (STEEL/ALUMINUM 등)
|
||
|
|
$itemCategory = $this->determineBendCategory($row['col1'] ?? '');
|
||
|
|
|
||
|
|
$itemData = [
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'item_type' => 'PT',
|
||
|
|
'item_category' => $itemCategory,
|
||
|
|
'code' => $code,
|
||
|
|
'name' => $name,
|
||
|
|
'unit' => '㎡',
|
||
|
|
'attributes' => json_encode([
|
||
|
|
'material_type' => $row['col1'] ?? null,
|
||
|
|
'material_sub' => $row['col2'] ?? null,
|
||
|
|
'width' => $this->parseNumber($row['col3'] ?? null),
|
||
|
|
'length' => $this->parseNumber($row['col4'] ?? null),
|
||
|
|
'thickness' => $this->parseNumber($row['col5'] ?? null),
|
||
|
|
'specific_gravity' => $this->parseNumber($row['col6'] ?? null),
|
||
|
|
'area' => $this->parseNumber($row['col7'] ?? null),
|
||
|
|
'weight' => $this->parseNumber($row['col8'] ?? null),
|
||
|
|
'purchase_price_per_kg' => $this->parseNumber($row['col9'] ?? null),
|
||
|
|
'loss_premium_price' => $this->parseNumber($row['col10'] ?? null),
|
||
|
|
'material_cost' => $this->parseNumber($row['col11'] ?? null),
|
||
|
|
'selling_price' => $this->parseNumber($row['col12'] ?? null),
|
||
|
|
'processing_cost_per_sqm' => $this->parseNumber($row['col13'] ?? null),
|
||
|
|
'processing_cost' => $this->parseNumber($row['col14'] ?? null),
|
||
|
|
'total' => $this->parseNumber($row['col15'] ?? null),
|
||
|
|
'price_per_sqm' => $this->parseNumber($row['col16'] ?? null),
|
||
|
|
'selling_price_per_sqm' => $this->parseNumber($row['col17'] ?? null),
|
||
|
|
'price_per_kg' => $this->parseNumber($row['col18'] ?? null),
|
||
|
|
'source' => '5130',
|
||
|
|
'source_table' => 'price_bend',
|
||
|
|
'source_id' => $latestRecord->num,
|
||
|
|
'source_version' => $latestRecord->registedate,
|
||
|
|
'source_order' => $orderNo,
|
||
|
|
], JSON_UNESCAPED_UNICODE),
|
||
|
|
'is_active' => true,
|
||
|
|
'created_at' => now(),
|
||
|
|
'updated_at' => now(),
|
||
|
|
];
|
||
|
|
|
||
|
|
if (! $dryRun) {
|
||
|
|
DB::connection($this->targetDb)->table('items')->insert($itemData);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->stats['bend']['migrated']++;
|
||
|
|
$bar->advance();
|
||
|
|
}
|
||
|
|
|
||
|
|
$bar->finish();
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("✅ price_bend 완료: {$this->stats['bend']['migrated']} migrated, {$this->stats['bend']['skipped']} skipped");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* item_type 결정 (KDunitprice용)
|
||
|
|
*
|
||
|
|
* 5130 item_div → SAM item_type 매핑:
|
||
|
|
* - [원재료] → RM (원자재)
|
||
|
|
* - [부재료] → SM (부자재)
|
||
|
|
* - [상품], [제품] → FG (완제품)
|
||
|
|
* - [반제품] → PT (부품)
|
||
|
|
* - [무형상품] → CS (소모품/서비스)
|
||
|
|
*
|
||
|
|
* 주의: 조건 순서가 중요함 (더 구체적인 조건을 먼저 체크)
|
||
|
|
* - '반제품'을 '제품'보다 먼저 체크
|
||
|
|
* - '무형상품'을 '상품'보다 먼저 체크
|
||
|
|
*/
|
||
|
|
private function determineItemType(?string $itemDiv): string
|
||
|
|
{
|
||
|
|
if (empty($itemDiv)) {
|
||
|
|
return 'SM';
|
||
|
|
}
|
||
|
|
|
||
|
|
$lower = mb_strtolower($itemDiv);
|
||
|
|
|
||
|
|
// 1. 부품 판별 (반제품) - '제품'보다 먼저!
|
||
|
|
if (str_contains($lower, '반제품')) {
|
||
|
|
return 'PT';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 소모품 판별 (무형상품) - '상품'보다 먼저!
|
||
|
|
if (str_contains($lower, '무형') || str_contains($lower, '소모품') || str_contains($lower, 'consumable') || str_contains($lower, '소모')) {
|
||
|
|
return 'CS';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 완제품 판별 (상품, 제품)
|
||
|
|
if (str_contains($lower, '상품') || str_contains($lower, '제품')) {
|
||
|
|
return 'FG';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. 원자재 판별 (원재료)
|
||
|
|
if (str_contains($lower, '원자재') || str_contains($lower, '원재료') || str_contains($lower, 'raw') || str_contains($lower, '원료')) {
|
||
|
|
return 'RM';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. 기본값: 부자재 (부재료 포함)
|
||
|
|
return 'SM';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 원자재 카테고리 결정
|
||
|
|
*/
|
||
|
|
private function determineRawMaterialCategory(?string $productType): string
|
||
|
|
{
|
||
|
|
if (empty($productType)) {
|
||
|
|
return 'GENERAL';
|
||
|
|
}
|
||
|
|
|
||
|
|
$lower = mb_strtolower($productType);
|
||
|
|
|
||
|
|
if (str_contains($lower, '슬랫') || str_contains($lower, 'slat')) {
|
||
|
|
return 'SLAT';
|
||
|
|
}
|
||
|
|
if (str_contains($lower, '알루미늄') || str_contains($lower, 'aluminum') || str_contains($lower, 'al')) {
|
||
|
|
return 'ALUMINUM';
|
||
|
|
}
|
||
|
|
if (str_contains($lower, '스틸') || str_contains($lower, 'steel')) {
|
||
|
|
return 'STEEL';
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'GENERAL';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 절곡 카테고리 결정
|
||
|
|
*/
|
||
|
|
private function determineBendCategory(?string $materialType): string
|
||
|
|
{
|
||
|
|
if (empty($materialType)) {
|
||
|
|
return 'GENERAL';
|
||
|
|
}
|
||
|
|
|
||
|
|
$lower = mb_strtolower($materialType);
|
||
|
|
|
||
|
|
if (str_contains($lower, '스틸') || str_contains($lower, 'steel') || str_contains($lower, '철')) {
|
||
|
|
return 'STEEL';
|
||
|
|
}
|
||
|
|
if (str_contains($lower, '알루미늄') || str_contains($lower, 'aluminum') || str_contains($lower, 'al')) {
|
||
|
|
return 'ALUMINUM';
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'BENDING';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 코드 생성
|
||
|
|
*/
|
||
|
|
private function generateCode(string $prefix, ?string $date, int $orderNo): string
|
||
|
|
{
|
||
|
|
$dateStr = $date ? str_replace('-', '', $date) : date('Ymd');
|
||
|
|
|
||
|
|
return sprintf('%s-%s-%03d', $prefix, $dateStr, $orderNo);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 숫자 파싱 (콤마, 공백 제거)
|
||
|
|
*/
|
||
|
|
private function parseNumber(mixed $value): ?float
|
||
|
|
{
|
||
|
|
if ($value === null || $value === '') {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_numeric($value)) {
|
||
|
|
return (float) $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
$cleaned = preg_replace('/[^\d.-]/', '', (string) $value);
|
||
|
|
|
||
|
|
return is_numeric($cleaned) ? (float) $cleaned : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 롤백
|
||
|
|
*/
|
||
|
|
private function rollbackMigration(int $tenantId, bool $dryRun): int
|
||
|
|
{
|
||
|
|
$this->warn('╔══════════════════════════════════════════════════════════════╗');
|
||
|
|
$this->warn('║ ⚠️ 롤백 모드 ║');
|
||
|
|
$this->warn('╚══════════════════════════════════════════════════════════════╝');
|
||
|
|
|
||
|
|
if (! $this->confirm('5130 가격표에서 마이그레이션된 모든 데이터를 삭제하시겠습니까?')) {
|
||
|
|
$this->info('롤백 취소됨');
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
$tables = ['KDunitprice', 'price_raw_materials', 'price_bend'];
|
||
|
|
|
||
|
|
foreach ($tables as $table) {
|
||
|
|
$count = DB::connection($this->targetDb)->table('items')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereRaw("JSON_EXTRACT(attributes, '$.source_table') = ?", [$table])
|
||
|
|
->count();
|
||
|
|
|
||
|
|
if (! $dryRun) {
|
||
|
|
DB::connection($this->targetDb)->table('items')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereRaw("JSON_EXTRACT(attributes, '$.source_table') = ?", [$table])
|
||
|
|
->delete();
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->line("Deleted {$count} items from {$table}");
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->info('✅ 롤백 완료');
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 요약 출력
|
||
|
|
*/
|
||
|
|
private function showSummary(): void
|
||
|
|
{
|
||
|
|
$this->newLine();
|
||
|
|
$this->info('╔══════════════════════════════════════════════════════════════╗');
|
||
|
|
$this->info('║ 📊 마이그레이션 결과 요약 ║');
|
||
|
|
$this->info('╚══════════════════════════════════════════════════════════════╝');
|
||
|
|
|
||
|
|
$this->table(
|
||
|
|
['Source Table', 'Total', 'Migrated', 'Skipped'],
|
||
|
|
[
|
||
|
|
['KDunitprice', $this->stats['kdunitprice']['total'], $this->stats['kdunitprice']['migrated'], $this->stats['kdunitprice']['skipped']],
|
||
|
|
['price_raw_materials', $this->stats['raw_materials']['total'], $this->stats['raw_materials']['migrated'], $this->stats['raw_materials']['skipped']],
|
||
|
|
['price_bend', $this->stats['bend']['total'], $this->stats['bend']['migrated'], $this->stats['bend']['skipped']],
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
$totalMigrated = $this->stats['kdunitprice']['migrated']
|
||
|
|
+ $this->stats['raw_materials']['migrated']
|
||
|
|
+ $this->stats['bend']['migrated'];
|
||
|
|
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("🎉 총 {$totalMigrated}개 품목 마이그레이션 완료!");
|
||
|
|
}
|
||
|
|
}
|