feat: BOM 테스트 및 데이터 마이그레이션

- BOM child_item_id를 새 items 테이블 ID로 마이그레이션
- Item.loadBomChildren() 수정: setRelation()으로 모델에 설정
- ItemService.validateBom() 추가: 순환 참조 방지
- error.php에 self_reference_bom 메시지 추가
- ID 7 자기참조 BOM 데이터 수정 완료

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-13 23:53:16 +09:00
parent 9cc7cd1428
commit d2bdecf063
4 changed files with 167 additions and 4 deletions

View File

@@ -0,0 +1,130 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* BOM JSON의 child_item_id를 이전 테이블(products/materials) ID에서
* 새 items 테이블 ID로 변환
*/
public function up(): void
{
// BOM이 있는 모든 items 조회
$items = DB::table('items')
->whereNotNull('bom')
->whereRaw('JSON_LENGTH(bom) > 0')
->get(['id', 'bom']);
$updated = 0;
$skipped = 0;
foreach ($items as $item) {
$bom = json_decode($item->bom, true);
if (empty($bom)) {
continue;
}
$newBom = [];
$hasChanges = false;
foreach ($bom as $entry) {
$childItemId = $entry['child_item_id'] ?? null;
$childItemType = $entry['child_item_type'] ?? null;
if (! $childItemId || ! $childItemType) {
$newBom[] = $entry;
continue;
}
// child_item_type에 따른 source_table 결정
$sourceTable = match (strtoupper($childItemType)) {
'MATERIAL' => 'materials',
'PRODUCT', 'SUBASSEMBLY', 'PART' => 'products',
default => null,
};
if (! $sourceTable) {
$newBom[] = $entry;
continue;
}
// item_id_mappings에서 새 item_id 조회
$mapping = DB::table('item_id_mappings')
->where('source_table', $sourceTable)
->where('source_id', $childItemId)
->first();
if ($mapping) {
$entry['child_item_id'] = $mapping->item_id;
$hasChanges = true;
}
$newBom[] = $entry;
}
if ($hasChanges) {
DB::table('items')
->where('id', $item->id)
->update(['bom' => json_encode($newBom)]);
$updated++;
} else {
$skipped++;
}
}
// 결과 로그
DB::statement("SELECT 'BOM migration: updated={$updated}, skipped={$skipped}' AS result");
}
/**
* 롤백: 새 item_id를 이전 ID로 복원
* 주의: 완벽한 롤백은 어려움 - 원본 BOM 백업 권장
*/
public function down(): void
{
// BOM이 있는 모든 items 조회
$items = DB::table('items')
->whereNotNull('bom')
->whereRaw('JSON_LENGTH(bom) > 0')
->get(['id', 'bom']);
foreach ($items as $item) {
$bom = json_decode($item->bom, true);
if (empty($bom)) {
continue;
}
$newBom = [];
$hasChanges = false;
foreach ($bom as $entry) {
$childItemId = $entry['child_item_id'] ?? null;
if (! $childItemId) {
$newBom[] = $entry;
continue;
}
// item_id_mappings에서 원본 ID 조회
$mapping = DB::table('item_id_mappings')
->where('item_id', $childItemId)
->first();
if ($mapping) {
$entry['child_item_id'] = $mapping->source_id;
$hasChanges = true;
}
$newBom[] = $entry;
}
if ($hasChanges) {
DB::table('items')
->where('id', $item->id)
->update(['bom' => json_encode($newBom)]);
}
}
}
};