fix: 경동 BOM 계산 수정 및 품목-공정 매핑
- KyungdongFormulaHandler: product_type 자동 추론(item_category 기반), 철재 주자재 EGI코일로 변경, 조인트바 steel 공통 지원 - FormulaEvaluatorService: FG item_category에서 product_type 자동 판별 - MapItemsToProcesses: 경동 품목-공정 매핑 커맨드 정비 - KyungdongItemMasterSeeder: BOM child_item_id code 기반 재매핑 - ItemsBomController: ghost ID 유효성 검증 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -76,7 +76,7 @@ public function run(): void
|
||||
|
||||
private function cleanup(): void
|
||||
{
|
||||
$this->command->info('CLEANUP: tenant_id=' . $this->tenantId . ' 데이터 삭제');
|
||||
$this->command->info('CLEANUP: tenant_id='.$this->tenantId.' 데이터 삭제');
|
||||
|
||||
// FK 역순 삭제
|
||||
// prices - tenant_id로 직접 삭제
|
||||
@@ -100,7 +100,7 @@ private function cleanup(): void
|
||||
->whereNotNull('parent_id')
|
||||
->delete();
|
||||
$c = DB::table('categories')->where('tenant_id', $this->tenantId)->delete();
|
||||
$this->command->info(" categories: " . count($catIds) . "건 삭제");
|
||||
$this->command->info(' categories: '.count($catIds).'건 삭제');
|
||||
|
||||
// entity_relationships
|
||||
$c = DB::table('entity_relationships')->where('tenant_id', $this->tenantId)->delete();
|
||||
@@ -140,7 +140,7 @@ private function seedItemPages(): void
|
||||
$this->pageMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_pages: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_pages: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItemSections(): void
|
||||
@@ -158,7 +158,7 @@ private function seedItemSections(): void
|
||||
$this->sectionMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_sections: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_sections: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItemFields(): void
|
||||
@@ -176,7 +176,7 @@ private function seedItemFields(): void
|
||||
$this->fieldMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_fields: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_fields: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedEntityRelationships(): void
|
||||
@@ -197,6 +197,7 @@ private function seedEntityRelationships(): void
|
||||
|
||||
if ($data['parent_id'] === null || $data['child_id'] === null) {
|
||||
$this->command->warn(" entity_relationships: 매핑 실패 건 스킵 (parent_type={$row['parent_type']}, child_type={$row['child_type']})");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -314,6 +315,7 @@ private function seedCategories(): void
|
||||
|
||||
if (! isset($this->categoryMap[$parentOriginalId])) {
|
||||
$nextRemaining[] = $row;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -329,10 +331,10 @@ private function seedCategories(): void
|
||||
}
|
||||
|
||||
if (! empty($remaining)) {
|
||||
$this->command->warn(" categories: " . count($remaining) . "건 매핑 실패 (depth 초과)");
|
||||
$this->command->warn(' categories: '.count($remaining).'건 매핑 실패 (depth 초과)');
|
||||
}
|
||||
|
||||
$this->command->info(" categories: " . count($this->categoryMap) . "건 삽입");
|
||||
$this->command->info(' categories: '.count($this->categoryMap).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItems(): void
|
||||
@@ -369,7 +371,57 @@ private function seedItems(): void
|
||||
->pluck('id', 'code')
|
||||
->toArray();
|
||||
|
||||
$this->command->info(" items: {$total}건 삽입 (itemMap: " . count($this->itemMap) . "건)");
|
||||
$this->command->info(" items: {$total}건 삽입 (itemMap: ".count($this->itemMap).'건)');
|
||||
|
||||
// BOM의 child_item_code → child_item_id 변환
|
||||
$this->remapBomCodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM JSON의 child_item_code를 실제 child_item_id로 변환
|
||||
*
|
||||
* items.json의 bom 필드가 child_item_code(품목코드) 기반으로 저장되어 있으므로,
|
||||
* INSERT 후 itemMap(code→id)을 이용해 child_item_id로 교체한다.
|
||||
*/
|
||||
private function remapBomCodes(): void
|
||||
{
|
||||
$bomItems = DB::table('items')
|
||||
->where('tenant_id', $this->tenantId)
|
||||
->whereNotNull('bom')
|
||||
->get(['id', 'code', 'bom']);
|
||||
|
||||
$updated = 0;
|
||||
|
||||
foreach ($bomItems as $item) {
|
||||
$bom = json_decode($item->bom, true);
|
||||
if (! is_array($bom)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = false;
|
||||
foreach ($bom as &$entry) {
|
||||
// child_item_code → child_item_id 변환
|
||||
if (isset($entry['child_item_code'])) {
|
||||
$code = $entry['child_item_code'];
|
||||
if (isset($this->itemMap[$code])) {
|
||||
$entry['child_item_id'] = $this->itemMap[$code];
|
||||
unset($entry['child_item_code']);
|
||||
$changed = true;
|
||||
} else {
|
||||
$this->command->warn(" BOM 매핑 실패: {$item->code} → {$code} (품목 미존재)");
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($entry);
|
||||
|
||||
if ($changed) {
|
||||
DB::table('items')->where('id', $item->id)
|
||||
->update(['bom' => json_encode($bom, JSON_UNESCAPED_UNICODE)]);
|
||||
$updated++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(" BOM code→id 매핑: {$updated}건 변환");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -399,6 +451,7 @@ private function seedItemDetails(): void
|
||||
$code = $originalIdToCode[$originalItemId] ?? null;
|
||||
if ($code === null || ! isset($this->itemMap[$code])) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -417,7 +470,7 @@ private function seedItemDetails(): void
|
||||
}
|
||||
|
||||
$this->expectedCounts['item_details'] = count($insertData);
|
||||
$this->command->info(" item_details: " . count($insertData) . "건 삽입");
|
||||
$this->command->info(' item_details: '.count($insertData).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedPrices(): void
|
||||
@@ -445,6 +498,7 @@ private function seedPrices(): void
|
||||
$code = $originalIdToCode[$originalItemId] ?? null;
|
||||
if ($code === null || ! isset($this->itemMap[$code])) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -463,7 +517,7 @@ private function seedPrices(): void
|
||||
}
|
||||
|
||||
$this->expectedCounts['prices'] = count($insertData);
|
||||
$this->command->info(" prices: " . count($insertData) . "건 삽입");
|
||||
$this->command->info(' prices: '.count($insertData).'건 삽입');
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -528,7 +582,7 @@ private function verifyRelationshipIntegrity(): void
|
||||
if ($orphans > 0) {
|
||||
$this->command->warn(" entity_relationships 참조 무결성: {$orphans}건 고아 레코드");
|
||||
} else {
|
||||
$this->command->info(" entity_relationships 참조 무결성: OK");
|
||||
$this->command->info(' entity_relationships 참조 무결성: OK');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,12 +601,12 @@ private function printSummary(): void
|
||||
{
|
||||
$this->command->newLine();
|
||||
$this->command->info('=== 경동기업 품목 기준 데이터 시더 완료 ===');
|
||||
$this->command->info(" 매핑 현황:");
|
||||
$this->command->info(" pageMap: " . count($this->pageMap) . "건");
|
||||
$this->command->info(" sectionMap: " . count($this->sectionMap) . "건");
|
||||
$this->command->info(" fieldMap: " . count($this->fieldMap) . "건");
|
||||
$this->command->info(" categoryMap: " . count($this->categoryMap) . "건");
|
||||
$this->command->info(" itemMap: " . count($this->itemMap) . "건");
|
||||
$this->command->info(' 매핑 현황:');
|
||||
$this->command->info(' pageMap: '.count($this->pageMap).'건');
|
||||
$this->command->info(' sectionMap: '.count($this->sectionMap).'건');
|
||||
$this->command->info(' fieldMap: '.count($this->fieldMap).'건');
|
||||
$this->command->info(' categoryMap: '.count($this->categoryMap).'건');
|
||||
$this->command->info(' itemMap: '.count($this->itemMap).'건');
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -561,11 +615,11 @@ private function printSummary(): void
|
||||
|
||||
private function loadJson(string $filename): array
|
||||
{
|
||||
$path = $this->dataPath . '/' . $filename;
|
||||
$path = $this->dataPath.'/'.$filename;
|
||||
|
||||
if (! file_exists($path)) {
|
||||
$this->command->error("JSON 파일 없음: {$path}");
|
||||
$this->command->error("먼저 php artisan kyungdong:export-item-master 를 실행하세요.");
|
||||
$this->command->error('먼저 php artisan kyungdong:export-item-master 를 실행하세요.');
|
||||
|
||||
throw new \RuntimeException("JSON 파일 없음: {$path}");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user