feat: Phase 2 BOM 마이그레이션 추가

- Phase 2.1: BDmodels.seconditem → PT items 6건 추가
  - 누락 부품: L-BAR, 보강평철, 케이스, 하단마감재 등
- Phase 2.2: items.bom JSON 연결 18건
  - FG items (models) ↔ PT items (seconditem) BOM 관계 설정
- 최종: items 634건, prices 634건, BOM 18건

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 21:06:59 +09:00
parent bacc42da73
commit 2e219edf8a

View File

@@ -12,6 +12,8 @@
* Phase 1.0: chandj.KDunitprice (601건) → items, prices * Phase 1.0: chandj.KDunitprice (601건) → items, prices
* Phase 1.1: chandj.models (18건) → items (FG), prices * Phase 1.1: chandj.models (18건) → items (FG), prices
* Phase 1.2: chandj.item_list (9건) → items (PT), prices * 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 연결)
* *
* @see docs/plans/kd-items-migration-plan.md * @see docs/plans/kd-items-migration-plan.md
*/ */
@@ -60,14 +62,21 @@ public function run(): void
// Phase 1.2: item_list → items (PT) // Phase 1.2: item_list → items (PT)
$itemListCount = $this->migrateItemList($tenantId, $userId); $itemListCount = $this->migrateItemList($tenantId, $userId);
// Phase 2.1: BDmodels.seconditem → items (PT) 누락 부품
$bdPartsCount = $this->migrateBDmodelsParts($tenantId, $userId);
// prices 생성 (모든 items 기반) // prices 생성 (모든 items 기반)
$priceCount = $this->migratePrices($tenantId, $userId); $priceCount = $this->migratePrices($tenantId, $userId);
$totalItems = $itemCount + $modelCount + $itemListCount; // Phase 2.2: BDmodels → items.bom JSON
$bomCount = $this->migrateBom($tenantId);
$totalItems = $itemCount + $modelCount + $itemListCount + $bdPartsCount;
$this->command->info(''); $this->command->info('');
$this->command->info('✅ 마이그레이션 완료:'); $this->command->info('✅ 마이그레이션 완료:');
$this->command->info(" → items: {$totalItems}건 (KDunitprice {$itemCount} + models {$modelCount} + item_list {$itemListCount})"); $this->command->info(" → items: {$totalItems}건 (KDunitprice {$itemCount} + models {$modelCount} + item_list {$itemListCount} + BDmodels부품 {$bdPartsCount})");
$this->command->info(" → prices: {$priceCount}"); $this->command->info(" → prices: {$priceCount}");
$this->command->info(" → BOM 연결: {$bomCount}");
} }
/** /**
@@ -382,4 +391,193 @@ private function mapItemType(?string $itemDiv): string
{ {
return self::ITEM_TYPE_MAP[$itemDiv] ?? 'SM'; return self::ITEM_TYPE_MAP[$itemDiv] ?? 'SM';
} }
/**
* Phase 2.1: BDmodels.seconditem → items (PT) 누락 부품 추가
*
* item_list에 없는 BDmodels.seconditem을 PT items로 생성
*/
private function migrateBDmodelsParts(int $tenantId, int $userId): int
{
$this->command->info('');
$this->command->info('📦 [Phase 2.1] BDmodels.seconditem → items (PT) 누락 부품...');
// BDmodels에서 고유한 seconditem 목록 조회
$bdSecondItems = DB::connection('chandj')
->table('BDmodels')
->where(function ($q) {
$q->where('is_deleted', 0)->orWhereNull('is_deleted');
})
->whereNotNull('seconditem')
->where('seconditem', '!=', '')
->distinct()
->pluck('seconditem');
// 이미 존재하는 PT items 코드 조회
$existingPtCodes = DB::table('items')
->where('tenant_id', $tenantId)
->where('item_type', 'PT')
->pluck('code')
->map(fn ($code) => str_starts_with($code, 'PT-') ? substr($code, 3) : $code)
->toArray();
$items = [];
$now = now();
foreach ($bdSecondItems as $secondItem) {
// 이미 PT items에 있으면 스킵
if (in_array($secondItem, $existingPtCodes)) {
continue;
}
$code = "PT-{$secondItem}";
$items[] = [
'tenant_id' => $tenantId,
'item_type' => 'PT',
'code' => $code,
'name' => $secondItem,
'unit' => 'EA',
'category_id' => null,
'process_type' => null,
'item_category' => null,
'bom' => null,
'attributes' => json_encode([
'legacy_source' => 'BDmodels_seconditem',
]),
'attributes_archive' => null,
'options' => null,
'description' => null,
'is_active' => true,
'created_by' => $userId,
'updated_by' => $userId,
'created_at' => $now,
'updated_at' => $now,
];
}
if (! empty($items)) {
DB::table('items')->insert($items);
}
$this->command->info(" → 소스 데이터: {$bdSecondItems->count()}건 (중복 제외 ".count($items).'건 신규)');
$this->command->info(' ✓ items (PT): '.count($items).'건 생성 완료');
return count($items);
}
/**
* Phase 2.2: BDmodels → items.bom JSON (FG ↔ PT 연결)
*
* models 기반 FG items에 BOM 연결
* bom: [{child_item_id: X, quantity: Y}, ...]
*/
private function migrateBom(int $tenantId): int
{
$this->command->info('');
$this->command->info('🔗 [Phase 2.2] BDmodels → items.bom JSON 연결...');
// PT items 조회 (code → id 매핑)
$ptItems = DB::table('items')
->where('tenant_id', $tenantId)
->where('item_type', 'PT')
->pluck('id', 'code')
->toArray();
// PT- prefix 없는 버전도 매핑 추가
$ptItemsByName = [];
foreach ($ptItems as $code => $id) {
$name = str_starts_with($code, 'PT-') ? substr($code, 3) : $code;
$ptItemsByName[$name] = $id;
}
// FG items 조회 (models 기반)
$fgItems = DB::table('items')
->where('tenant_id', $tenantId)
->where('item_type', 'FG')
->whereNotNull('attributes')
->get(['id', 'code', 'attributes']);
// BDmodels 데이터 조회
$bdModels = DB::connection('chandj')
->table('BDmodels')
->where(function ($q) {
$q->where('is_deleted', 0)->orWhereNull('is_deleted');
})
->whereNotNull('model_name')
->where('model_name', '!=', '')
->get(['model_name', 'seconditem', 'savejson']);
// model_name → seconditems 그룹핑
$modelBomMap = [];
foreach ($bdModels as $bd) {
if (empty($bd->seconditem)) {
continue;
}
$modelName = $bd->model_name;
if (! isset($modelBomMap[$modelName])) {
$modelBomMap[$modelName] = [];
}
// savejson에서 수량 파싱 (col8이 수량)
$quantity = 1;
if (! empty($bd->savejson)) {
$json = json_decode($bd->savejson, true);
if (is_array($json) && ! empty($json)) {
// 첫 번째 항목의 col8(수량) 사용
$quantity = (int) ($json[0]['col8'] ?? 1);
}
}
// 중복 체크 후 추가
$found = false;
foreach ($modelBomMap[$modelName] as &$existing) {
if ($existing['seconditem'] === $bd->seconditem) {
$found = true;
break;
}
}
if (! $found) {
$modelBomMap[$modelName][] = [
'seconditem' => $bd->seconditem,
'quantity' => $quantity,
];
}
}
$updatedCount = 0;
foreach ($fgItems as $fgItem) {
$attributes = json_decode($fgItem->attributes, true) ?? [];
$modelName = $attributes['model_name'] ?? null;
if (empty($modelName) || ! isset($modelBomMap[$modelName])) {
continue;
}
$bomArray = [];
foreach ($modelBomMap[$modelName] as $bomItem) {
$childItemId = $ptItemsByName[$bomItem['seconditem']] ?? null;
if ($childItemId) {
$bomArray[] = [
'child_item_id' => $childItemId,
'quantity' => $bomItem['quantity'],
];
}
}
if (! empty($bomArray)) {
DB::table('items')
->where('id', $fgItem->id)
->update(['bom' => json_encode($bomArray)]);
$updatedCount++;
}
}
$this->command->info(' → BDmodels 모델: '.count($modelBomMap).'개');
$this->command->info(" ✓ items.bom 연결: {$updatedCount}건 완료");
return $updatedCount;
}
} }