- Migrate5130BendingStock: BD-* 품목 초기 재고 셋팅으로 목적 변경, --min-stock 옵션 추가 - ValidateBendingItems: BD-* 품목 존재 여부 검증 커맨드 신규 - BendingItemSeeder: 경동 절곡 품목 시딩 신규 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
631 lines
24 KiB
PHP
631 lines
24 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Attributes\AsCommand;
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
#[AsCommand(name: 'migrate:5130-bending-stock', description: '5130 레거시 절곡품 코드 생성 + BD-* 전체 품목 초기 재고 셋팅')]
|
||
class Migrate5130BendingStock extends Command
|
||
{
|
||
protected $signature = 'migrate:5130-bending-stock
|
||
{--tenant_id=287 : Target tenant ID (default: 287 경동기업)}
|
||
{--dry-run : 실제 저장 없이 시뮬레이션만 수행}
|
||
{--min-stock=100 : 품목별 초기 재고 수량 (기본: 100)}
|
||
{--rollback : 초기 재고 셋팅 롤백 (init_stock 소스 데이터 삭제)}';
|
||
|
||
private string $sourceDb = 'chandj';
|
||
|
||
private string $targetDb = 'mysql';
|
||
|
||
// 5130 prod 코드 → 한글명
|
||
private array $prodNames = [
|
||
'R' => '가이드레일(벽면)', 'S' => '가이드레일(측면)',
|
||
'G' => '연기차단재', 'B' => '하단마감재(스크린)',
|
||
'T' => '하단마감재(철재)', 'L' => 'L-Bar', 'C' => '케이스',
|
||
];
|
||
|
||
// 5130 spec 코드 → 한글명
|
||
private array $specNames = [
|
||
'I' => '화이바원단', 'S' => 'SUS', 'U' => 'SUS2', 'E' => 'EGI',
|
||
'A' => '스크린용', 'D' => 'D형', 'C' => 'C형', 'M' => '본체',
|
||
'T' => '본체(철재)', 'B' => '후면코너부', 'L' => '린텔부',
|
||
'P' => '점검구', 'F' => '전면부',
|
||
];
|
||
|
||
// 5130 slength 코드 → 한글명
|
||
private array $slengthNames = [
|
||
'53' => 'W50×3000', '54' => 'W50×4000', '83' => 'W80×3000',
|
||
'84' => 'W80×4000', '12' => '1219mm', '24' => '2438mm',
|
||
'30' => '3000mm', '35' => '3500mm', '40' => '4000mm',
|
||
'41' => '4150mm', '42' => '4200mm', '43' => '4300mm',
|
||
];
|
||
|
||
private array $stats = [
|
||
'items_found' => 0,
|
||
'items_created_5130' => 0,
|
||
'items_category_updated' => 0,
|
||
'stocks_created' => 0,
|
||
'stocks_skipped' => 0,
|
||
'lots_created' => 0,
|
||
'transactions_created' => 0,
|
||
];
|
||
|
||
public function handle(): int
|
||
{
|
||
$tenantId = (int) $this->option('tenant_id');
|
||
$dryRun = $this->option('dry-run');
|
||
$rollback = $this->option('rollback');
|
||
$minStock = (int) $this->option('min-stock');
|
||
|
||
$this->info('=== BD-* 절곡품 초기 재고 셋팅 ===');
|
||
$this->info("Tenant ID: {$tenantId}");
|
||
$this->info('Mode: '.($dryRun ? 'DRY-RUN (시뮬레이션)' : 'LIVE'));
|
||
$this->info("초기 재고: {$minStock}개/품목");
|
||
$this->newLine();
|
||
|
||
if ($rollback) {
|
||
return $this->rollbackInitStock($tenantId, $dryRun);
|
||
}
|
||
|
||
// 0. 5130 레거시 데이터에서 BD-{PROD}{SPEC}-{SLENGTH} 아이템 생성
|
||
$this->info('📥 Step 0: 5130 레거시 코드 → BD 아이템 생성...');
|
||
$this->createLegacyItems($tenantId, $dryRun);
|
||
$this->newLine();
|
||
|
||
// 1. 전체 BD-* 아이템 조회 (기존 58개 + 5130 생성분)
|
||
$this->info('📥 Step 1: BD-* 절곡품 품목 조회...');
|
||
$items = DB::connection($this->targetDb)
|
||
->table('items')
|
||
->where('tenant_id', $tenantId)
|
||
->where('code', 'like', 'BD-%')
|
||
->whereNull('deleted_at')
|
||
->select('id', 'code', 'name', 'item_type', 'item_category', 'unit', 'options')
|
||
->orderBy('code')
|
||
->get();
|
||
|
||
$this->stats['items_found'] = $items->count();
|
||
$this->info(" - BD-* 품목: {$items->count()}건");
|
||
|
||
if ($items->isEmpty()) {
|
||
$this->warn('BD-* 품목이 없습니다. 종료합니다.');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
// 2. item_category 미설정 품목 업데이트
|
||
$this->newLine();
|
||
$this->info('🏷️ Step 2: item_category 업데이트...');
|
||
$needsCategoryUpdate = $items->filter(fn ($item) => $item->item_category !== 'BENDING');
|
||
|
||
if ($needsCategoryUpdate->isNotEmpty()) {
|
||
$this->info(" - item_category 미설정/불일치: {$needsCategoryUpdate->count()}건");
|
||
if (! $dryRun) {
|
||
DB::connection($this->targetDb)
|
||
->table('items')
|
||
->where('tenant_id', $tenantId)
|
||
->where('code', 'like', 'BD-%')
|
||
->whereNull('deleted_at')
|
||
->where(function ($q) {
|
||
$q->whereNull('item_category')
|
||
->orWhere('item_category', '!=', 'BENDING');
|
||
})
|
||
->update(['item_category' => 'BENDING', 'updated_at' => now()]);
|
||
}
|
||
$this->stats['items_category_updated'] = $needsCategoryUpdate->count();
|
||
} else {
|
||
$this->info(' - 모든 품목 BENDING 카테고리 설정 완료');
|
||
}
|
||
|
||
// 3. 현재 재고 현황 표시
|
||
$this->newLine();
|
||
$this->info('📊 Step 3: 현재 재고 현황...');
|
||
$this->showCurrentStockStatus($tenantId, $items);
|
||
|
||
// 4. 재고 셋팅 대상 확인
|
||
$this->newLine();
|
||
$this->info('📦 Step 4: 재고 셋팅 대상 확인...');
|
||
$itemsNeedingStock = $this->getItemsNeedingStock($tenantId, $items, $minStock);
|
||
|
||
if ($itemsNeedingStock->isEmpty()) {
|
||
$this->info(" - 모든 품목이 이미 {$minStock}개 이상 재고 보유. 추가 작업 불필요.");
|
||
$this->showStats();
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
$this->info(" - 재고 셋팅 필요: {$itemsNeedingStock->count()}건");
|
||
$this->table(
|
||
['코드', '품목명', '현재고', '목표', '추가수량'],
|
||
$itemsNeedingStock->map(fn ($item) => [
|
||
$item->code,
|
||
mb_strlen($item->name) > 30 ? mb_substr($item->name, 0, 30).'...' : $item->name,
|
||
number_format($item->current_qty),
|
||
number_format($minStock),
|
||
number_format($item->supplement_qty),
|
||
])->toArray()
|
||
);
|
||
|
||
if ($dryRun) {
|
||
$this->stats['stocks_created'] = $itemsNeedingStock->filter(fn ($i) => ! $i->has_stock)->count();
|
||
$this->stats['lots_created'] = $itemsNeedingStock->count();
|
||
$this->stats['transactions_created'] = $itemsNeedingStock->count();
|
||
$this->showStats();
|
||
$this->info('🔍 DRY RUN 완료. 실제 실행은 --dry-run 플래그를 제거하세요.');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
if (! $this->confirm('초기 재고를 셋팅하시겠습니까?')) {
|
||
$this->info('취소되었습니다.');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
// 5. 실행
|
||
$this->newLine();
|
||
$this->info('🚀 Step 5: 초기 재고 셋팅 실행...');
|
||
DB::connection($this->targetDb)->transaction(function () use ($tenantId, $itemsNeedingStock, $minStock) {
|
||
$this->executeStockSetup($tenantId, $itemsNeedingStock, $minStock);
|
||
});
|
||
|
||
$this->newLine();
|
||
$this->showStats();
|
||
$this->info('✅ 초기 재고 셋팅 완료!');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* 현재 재고 현황 표시
|
||
*/
|
||
private function showCurrentStockStatus(int $tenantId, \Illuminate\Support\Collection $items): void
|
||
{
|
||
$itemIds = $items->pluck('id');
|
||
|
||
$stocks = DB::connection($this->targetDb)
|
||
->table('stocks')
|
||
->where('tenant_id', $tenantId)
|
||
->whereIn('item_id', $itemIds)
|
||
->whereNull('deleted_at')
|
||
->get()
|
||
->keyBy('item_id');
|
||
|
||
$hasStock = 0;
|
||
$noStock = 0;
|
||
|
||
foreach ($items as $item) {
|
||
$stock = $stocks->get($item->id);
|
||
if ($stock && (float) $stock->stock_qty > 0) {
|
||
$hasStock++;
|
||
} else {
|
||
$noStock++;
|
||
}
|
||
}
|
||
|
||
$this->info(" - 재고 있음: {$hasStock}건");
|
||
$this->info(" - 재고 없음: {$noStock}건");
|
||
}
|
||
|
||
/**
|
||
* 재고 셋팅이 필요한 품목 목록 조회
|
||
*/
|
||
private function getItemsNeedingStock(int $tenantId, \Illuminate\Support\Collection $items, int $minStock): \Illuminate\Support\Collection
|
||
{
|
||
$itemIds = $items->pluck('id');
|
||
|
||
$stocks = DB::connection($this->targetDb)
|
||
->table('stocks')
|
||
->where('tenant_id', $tenantId)
|
||
->whereIn('item_id', $itemIds)
|
||
->whereNull('deleted_at')
|
||
->get()
|
||
->keyBy('item_id');
|
||
|
||
$result = collect();
|
||
|
||
foreach ($items as $item) {
|
||
$stock = $stocks->get($item->id);
|
||
$currentQty = $stock ? (float) $stock->stock_qty : 0;
|
||
|
||
if ($currentQty >= $minStock) {
|
||
$this->stats['stocks_skipped']++;
|
||
|
||
continue;
|
||
}
|
||
|
||
$supplementQty = $minStock - $currentQty;
|
||
|
||
$item->has_stock = (bool) $stock;
|
||
$item->stock_id = $stock?->id;
|
||
$item->current_qty = $currentQty;
|
||
$item->supplement_qty = $supplementQty;
|
||
|
||
$result->push($item);
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 초기 재고 셋팅 실행
|
||
*/
|
||
private function executeStockSetup(int $tenantId, \Illuminate\Support\Collection $items, int $minStock): void
|
||
{
|
||
foreach ($items as $item) {
|
||
$stockId = $item->stock_id;
|
||
|
||
// Stock 레코드가 없으면 생성
|
||
if (! $item->has_stock) {
|
||
$stockId = DB::connection($this->targetDb)->table('stocks')->insertGetId([
|
||
'tenant_id' => $tenantId,
|
||
'item_id' => $item->id,
|
||
'item_code' => $item->code,
|
||
'item_name' => $item->name,
|
||
'item_type' => 'bent_part',
|
||
'unit' => $item->unit ?? 'EA',
|
||
'stock_qty' => 0,
|
||
'safety_stock' => 0,
|
||
'reserved_qty' => 0,
|
||
'available_qty' => 0,
|
||
'lot_count' => 0,
|
||
'status' => 'out',
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
$this->stats['stocks_created']++;
|
||
$this->line(" + Stock 생성: {$item->code}");
|
||
}
|
||
|
||
// FIFO 순서 계산
|
||
$maxFifo = DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->where('stock_id', $stockId)
|
||
->max('fifo_order');
|
||
$nextFifo = ($maxFifo ?? 0) + 1;
|
||
|
||
// LOT 번호 생성
|
||
$lotNo = 'INIT-'.now()->format('ymd').'-'.str_replace(['-', ' ', '*'], ['', '', 'x'], $item->code);
|
||
|
||
// 중복 체크
|
||
$existingLot = DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->where('tenant_id', $tenantId)
|
||
->where('stock_id', $stockId)
|
||
->where('lot_no', $lotNo)
|
||
->whereNull('deleted_at')
|
||
->first();
|
||
|
||
if ($existingLot) {
|
||
$this->warn(" ⚠️ 이미 LOT 존재 (skip): {$lotNo}");
|
||
|
||
continue;
|
||
}
|
||
|
||
$supplementQty = $item->supplement_qty;
|
||
|
||
// StockLot 생성
|
||
$stockLotId = DB::connection($this->targetDb)->table('stock_lots')->insertGetId([
|
||
'tenant_id' => $tenantId,
|
||
'stock_id' => $stockId,
|
||
'lot_no' => $lotNo,
|
||
'fifo_order' => $nextFifo,
|
||
'receipt_date' => now()->toDateString(),
|
||
'qty' => $supplementQty,
|
||
'reserved_qty' => 0,
|
||
'available_qty' => $supplementQty,
|
||
'unit' => $item->unit ?? 'EA',
|
||
'status' => 'available',
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
$this->stats['lots_created']++;
|
||
|
||
// StockTransaction 생성
|
||
DB::connection($this->targetDb)->table('stock_transactions')->insert([
|
||
'tenant_id' => $tenantId,
|
||
'stock_id' => $stockId,
|
||
'stock_lot_id' => $stockLotId,
|
||
'type' => 'IN',
|
||
'qty' => $supplementQty,
|
||
'balance_qty' => 0,
|
||
'reference_type' => 'init_stock',
|
||
'reference_id' => 0,
|
||
'lot_no' => $lotNo,
|
||
'reason' => 'receiving',
|
||
'remark' => "절곡품 초기 재고 셋팅 (min-stock={$minStock})",
|
||
'item_code' => $item->code,
|
||
'item_name' => $item->name,
|
||
'created_at' => now(),
|
||
]);
|
||
$this->stats['transactions_created']++;
|
||
|
||
// Stock 집계 갱신
|
||
$this->refreshStockFromLots($stockId, $tenantId);
|
||
|
||
$this->line(" ✅ {$item->code}: 0 → {$supplementQty} (+{$supplementQty})");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Stock 집계 갱신 (LOT 기반)
|
||
*/
|
||
private function refreshStockFromLots(int $stockId, int $tenantId): void
|
||
{
|
||
$lotStats = DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->where('stock_id', $stockId)
|
||
->where('tenant_id', $tenantId)
|
||
->whereNull('deleted_at')
|
||
->selectRaw('
|
||
COALESCE(SUM(qty), 0) as total_qty,
|
||
COALESCE(SUM(reserved_qty), 0) as total_reserved,
|
||
COALESCE(SUM(available_qty), 0) as total_available,
|
||
COUNT(*) as lot_count,
|
||
MIN(receipt_date) as oldest_lot_date,
|
||
MAX(receipt_date) as latest_receipt_date
|
||
')
|
||
->first();
|
||
|
||
$stockQty = (float) $lotStats->total_qty;
|
||
|
||
DB::connection($this->targetDb)
|
||
->table('stocks')
|
||
->where('id', $stockId)
|
||
->update([
|
||
'stock_qty' => $stockQty,
|
||
'reserved_qty' => (float) $lotStats->total_reserved,
|
||
'available_qty' => (float) $lotStats->total_available,
|
||
'lot_count' => (int) $lotStats->lot_count,
|
||
'oldest_lot_date' => $lotStats->oldest_lot_date,
|
||
'last_receipt_date' => $lotStats->latest_receipt_date,
|
||
'status' => $stockQty > 0 ? 'normal' : 'out',
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 롤백: init_stock 참조 데이터 삭제
|
||
*/
|
||
private function rollbackInitStock(int $tenantId, bool $dryRun): int
|
||
{
|
||
$this->warn('⚠️ 롤백: 초기 재고 셋팅 데이터를 삭제합니다.');
|
||
|
||
// init_stock으로 생성된 트랜잭션
|
||
$txCount = DB::connection($this->targetDb)
|
||
->table('stock_transactions')
|
||
->where('tenant_id', $tenantId)
|
||
->where('reference_type', 'init_stock')
|
||
->count();
|
||
|
||
// init_stock 트랜잭션에 연결된 LOT
|
||
$lotIds = DB::connection($this->targetDb)
|
||
->table('stock_transactions')
|
||
->where('tenant_id', $tenantId)
|
||
->where('reference_type', 'init_stock')
|
||
->whereNotNull('stock_lot_id')
|
||
->pluck('stock_lot_id')
|
||
->unique();
|
||
|
||
// 5130으로 생성된 아이템
|
||
$legacyItemCount = DB::connection($this->targetDb)
|
||
->table('items')
|
||
->where('tenant_id', $tenantId)
|
||
->where('options->source', '5130_migration')
|
||
->whereNull('deleted_at')
|
||
->count();
|
||
|
||
$this->info(' 삭제 대상:');
|
||
$this->info(" - stock_transactions (reference_type=init_stock): {$txCount}건");
|
||
$this->info(" - stock_lots (연결 LOT): {$lotIds->count()}건");
|
||
$this->info(" - items (source=5130_migration): {$legacyItemCount}건");
|
||
|
||
if ($dryRun) {
|
||
$this->info('DRY RUN - 실제 삭제 없음');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
if (! $this->confirm('정말 롤백하시겠습니까? 되돌릴 수 없습니다.')) {
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
DB::connection($this->targetDb)->transaction(function () use ($tenantId, $lotIds) {
|
||
// 1. 트랜잭션 삭제
|
||
DB::connection($this->targetDb)
|
||
->table('stock_transactions')
|
||
->where('tenant_id', $tenantId)
|
||
->where('reference_type', 'init_stock')
|
||
->delete();
|
||
|
||
// 2. LOT에서 stock_id 목록 수집 (집계 갱신용)
|
||
$affectedStockIds = collect();
|
||
if ($lotIds->isNotEmpty()) {
|
||
$affectedStockIds = DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->whereIn('id', $lotIds)
|
||
->pluck('stock_id')
|
||
->unique();
|
||
|
||
// LOT 삭제
|
||
DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->whereIn('id', $lotIds)
|
||
->delete();
|
||
}
|
||
|
||
// 3. 영향받은 Stock 집계 갱신
|
||
foreach ($affectedStockIds as $stockId) {
|
||
$this->refreshStockFromLots($stockId, $tenantId);
|
||
}
|
||
|
||
// 4. 5130 migration으로 생성된 아이템 + 연결 stocks 삭제
|
||
$migrationItemIds = DB::connection($this->targetDb)
|
||
->table('items')
|
||
->where('tenant_id', $tenantId)
|
||
->where('options->source', '5130_migration')
|
||
->whereNull('deleted_at')
|
||
->pluck('id');
|
||
|
||
if ($migrationItemIds->isNotEmpty()) {
|
||
$migrationStockIds = DB::connection($this->targetDb)
|
||
->table('stocks')
|
||
->where('tenant_id', $tenantId)
|
||
->whereIn('item_id', $migrationItemIds)
|
||
->pluck('id');
|
||
|
||
if ($migrationStockIds->isNotEmpty()) {
|
||
DB::connection($this->targetDb)
|
||
->table('stock_lots')
|
||
->whereIn('stock_id', $migrationStockIds)
|
||
->delete();
|
||
|
||
DB::connection($this->targetDb)
|
||
->table('stocks')
|
||
->whereIn('id', $migrationStockIds)
|
||
->delete();
|
||
}
|
||
|
||
DB::connection($this->targetDb)
|
||
->table('items')
|
||
->whereIn('id', $migrationItemIds)
|
||
->delete();
|
||
}
|
||
});
|
||
|
||
$this->info('✅ 롤백 완료');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* 5130 레거시 데이터에서 BD-{PROD}{SPEC}-{SLENGTH} 아이템 생성
|
||
*/
|
||
private function createLegacyItems(int $tenantId, bool $dryRun): void
|
||
{
|
||
// 5130 lot 테이블에서 고유 prod+spec+slength 조합 추출
|
||
$lots = DB::connection($this->sourceDb)
|
||
->table('lot')
|
||
->where(function ($q) {
|
||
$q->whereNull('is_deleted')
|
||
->orWhere('is_deleted', 0);
|
||
})
|
||
->whereNotNull('prod')
|
||
->where('prod', '!=', '')
|
||
->whereNotNull('surang')
|
||
->where('surang', '>', 0)
|
||
->select('prod', 'spec', 'slength')
|
||
->distinct()
|
||
->get();
|
||
|
||
// bending_work_log 테이블에서도 추출 (lot에 없는 조합 포함)
|
||
$workLogs = DB::connection($this->sourceDb)
|
||
->table('bending_work_log')
|
||
->where(function ($q) {
|
||
$q->whereNull('is_deleted')
|
||
->orWhere('is_deleted', 0);
|
||
})
|
||
->whereNotNull('prod_code')
|
||
->where('prod_code', '!=', '')
|
||
->select('prod_code as prod', 'spec_code as spec', 'slength_code as slength')
|
||
->distinct()
|
||
->get();
|
||
|
||
$allRecords = $lots->merge($workLogs);
|
||
|
||
if ($allRecords->isEmpty()) {
|
||
$this->info(' - 5130 데이터 없음');
|
||
|
||
return;
|
||
}
|
||
|
||
// 고유 제품 조합 추출
|
||
$uniqueProducts = [];
|
||
foreach ($allRecords as $row) {
|
||
$key = trim($row->prod).'-'.trim($row->spec ?? '').'-'.trim($row->slength ?? '');
|
||
if (! isset($uniqueProducts[$key])) {
|
||
$uniqueProducts[$key] = [
|
||
'prod' => trim($row->prod),
|
||
'spec' => trim($row->spec ?? ''),
|
||
'slength' => trim($row->slength ?? ''),
|
||
];
|
||
}
|
||
}
|
||
|
||
$this->info(" - 5130 고유 제품 조합: ".count($uniqueProducts).'개');
|
||
|
||
$created = 0;
|
||
$skipped = 0;
|
||
|
||
foreach ($uniqueProducts as $data) {
|
||
$itemCode = "BD-{$data['prod']}{$data['spec']}-{$data['slength']}";
|
||
$prodName = $this->prodNames[$data['prod']] ?? $data['prod'];
|
||
$specName = $this->specNames[$data['spec']] ?? $data['spec'];
|
||
$slengthName = $this->slengthNames[$data['slength']] ?? $data['slength'];
|
||
$itemName = implode(' ', array_filter([$prodName, $specName, $slengthName]));
|
||
|
||
// 이미 존재하는지 확인
|
||
$existing = DB::connection($this->targetDb)
|
||
->table('items')
|
||
->where('tenant_id', $tenantId)
|
||
->where('code', $itemCode)
|
||
->whereNull('deleted_at')
|
||
->first();
|
||
|
||
if ($existing) {
|
||
$skipped++;
|
||
|
||
continue;
|
||
}
|
||
|
||
if (! $dryRun) {
|
||
DB::connection($this->targetDb)->table('items')->insert([
|
||
'tenant_id' => $tenantId,
|
||
'code' => $itemCode,
|
||
'name' => $itemName,
|
||
'item_type' => 'PT',
|
||
'item_category' => 'BENDING',
|
||
'unit' => 'EA',
|
||
'options' => json_encode([
|
||
'source' => '5130_migration',
|
||
'lot_managed' => true,
|
||
'consumption_method' => 'auto',
|
||
'production_source' => 'self_produced',
|
||
'input_tracking' => true,
|
||
'legacy_prod' => $data['prod'],
|
||
'legacy_spec' => $data['spec'],
|
||
'legacy_slength' => $data['slength'],
|
||
]),
|
||
'is_active' => true,
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
$created++;
|
||
}
|
||
|
||
$this->stats['items_created_5130'] = $created;
|
||
$this->info(" - 신규 생성: {$created}건, 기존 존재 (skip): {$skipped}건");
|
||
}
|
||
|
||
/**
|
||
* 통계 출력
|
||
*/
|
||
private function showStats(): void
|
||
{
|
||
$this->newLine();
|
||
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
$this->info('📊 실행 통계');
|
||
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
$this->info(" 5130 아이템 생성: {$this->stats['items_created_5130']}건");
|
||
$this->info(" BD-* 품목 수 (전체): {$this->stats['items_found']}건");
|
||
$this->info(" 카테고리 업데이트: {$this->stats['items_category_updated']}건");
|
||
$this->info(" Stock 레코드 생성: {$this->stats['stocks_created']}건");
|
||
$this->info(" 기존 재고 충분 (skip): {$this->stats['stocks_skipped']}건");
|
||
$this->info(" StockLot 생성: {$this->stats['lots_created']}건");
|
||
$this->info(" 입고 트랜잭션: {$this->stats['transactions_created']}건");
|
||
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
}
|
||
} |