feat: Items 테이블 통합 마이그레이션 Phase 0-5 구현
## 주요 변경사항 - Phase 0: 비표준 item_type 데이터 정규화 마이그레이션 - Phase 1.1: items 테이블 생성 (products + materials 통합) - Phase 1.2: item_details 테이블 생성 (1:1 확장 필드) - Phase 1.3: 데이터 이관 + item_id_mappings 테이블 생성 - Phase 3: item_pages.source_table 업데이트 - Phase 5: 참조 테이블 마이그레이션 (product_components, orders 등) ## 신규 파일 - app/Models/Items/Item.php - 통합 아이템 모델 - app/Models/Items/ItemDetail.php - 1:1 확장 필드 모델 - app/Services/ItemService.php - 통합 서비스 클래스 ## 수정 파일 - ItemPage.php - items 테이블 지원 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Phase 0: 데이터 정규화
|
||||
*
|
||||
* products 테이블에서 비표준 item_type 삭제
|
||||
* - 표준: FG(완제품), PT(부품)
|
||||
* - 비표준(삭제): PRODUCT, SUBASSEMBLY, PART, CS
|
||||
*
|
||||
* 개발 중이므로 비표준 데이터 삭제 처리
|
||||
* 품목관리 완료 후 경동기업 데이터 전체 재세팅 예정
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 1. 삭제 전 건수 확인
|
||||
$beforeCount = DB::table('products')->count();
|
||||
$nonStandardCount = DB::table('products')
|
||||
->whereNotIn('product_type', ['FG', 'PT'])
|
||||
->count();
|
||||
|
||||
Log::info("Phase 0: Normalizing item_types", [
|
||||
'total_before' => $beforeCount,
|
||||
'non_standard_count' => $nonStandardCount,
|
||||
]);
|
||||
|
||||
// 2. 비표준 products와 관련된 product_components 삭제
|
||||
$bomDeleted = DB::table('product_components')
|
||||
->whereIn('parent_product_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('products')
|
||||
->whereNotIn('product_type', ['FG', 'PT']);
|
||||
})
|
||||
->orWhere(function ($query) {
|
||||
$query->where('ref_type', 'PRODUCT')
|
||||
->whereIn('ref_id', function ($q) {
|
||||
$q->select('id')
|
||||
->from('products')
|
||||
->whereNotIn('product_type', ['FG', 'PT']);
|
||||
});
|
||||
})
|
||||
->delete();
|
||||
|
||||
// 3. 비표준 products 삭제 (Soft Delete 아닌 Hard Delete)
|
||||
$deleted = DB::table('products')
|
||||
->whereNotIn('product_type', ['FG', 'PT'])
|
||||
->delete();
|
||||
|
||||
// 4. 삭제 후 건수 확인
|
||||
$afterCount = DB::table('products')->count();
|
||||
|
||||
Log::info("Phase 0: Normalization complete", [
|
||||
'bom_deleted' => $bomDeleted,
|
||||
'products_deleted' => $deleted,
|
||||
'total_after' => $afterCount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* 삭제된 데이터는 복구 불가 (개발 중이므로 허용)
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Log::warning("Phase 0 rollback: Deleted data cannot be restored");
|
||||
}
|
||||
};
|
||||
71
database/migrations/2025_12_13_152507_create_items_table.php
Normal file
71
database/migrations/2025_12_13_152507_create_items_table.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1.1: items 테이블 생성
|
||||
*
|
||||
* products + materials 통합 테이블
|
||||
* item_type: FG(완제품), PT(부품), SM(부자재), RM(원자재), CS(소모품)
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('items', function (Blueprint $table) {
|
||||
$table->id()->comment('ID');
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
|
||||
// 기본 정보
|
||||
$table->string('item_type', 15)->comment('FG, PT, SM, RM, CS');
|
||||
$table->string('code', 100)->comment('품목코드');
|
||||
$table->string('name', 255)->comment('품목명');
|
||||
$table->string('unit', 20)->nullable()->comment('단위');
|
||||
$table->foreignId('category_id')->nullable()->comment('카테고리 ID');
|
||||
|
||||
// BOM (JSON) - child_item_id, quantity
|
||||
$table->json('bom')->nullable()->comment('[{child_item_id, quantity}, ...]');
|
||||
|
||||
// 동적 속성
|
||||
$table->json('attributes')->nullable()->comment('동적 필드 값');
|
||||
$table->json('attributes_archive')->nullable()->comment('속성 아카이브');
|
||||
$table->json('options')->nullable()->comment('추가 옵션');
|
||||
|
||||
// 설명
|
||||
$table->text('description')->nullable()->comment('설명');
|
||||
|
||||
// 상태
|
||||
$table->boolean('is_active')->default(true)->comment('활성 여부');
|
||||
|
||||
// 감사 필드
|
||||
$table->foreignId('created_by')->nullable()->comment('생성자 ID');
|
||||
$table->foreignId('updated_by')->nullable()->comment('수정자 ID');
|
||||
$table->foreignId('deleted_by')->nullable()->comment('삭제자 ID');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스
|
||||
$table->index(['tenant_id', 'item_type'], 'idx_items_tenant_type');
|
||||
$table->index(['tenant_id', 'code'], 'idx_items_tenant_code');
|
||||
$table->index(['tenant_id', 'category_id'], 'idx_items_tenant_category');
|
||||
$table->unique(['tenant_id', 'code', 'deleted_at'], 'uq_items_tenant_code');
|
||||
|
||||
// 외래키
|
||||
$table->foreign('tenant_id')->references('id')->on('tenants');
|
||||
$table->foreign('category_id')->references('id')->on('categories')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1.2: item_details 테이블 생성
|
||||
*
|
||||
* items 테이블의 확장 필드 (1:1 관계)
|
||||
* - Products 전용 필드 (is_sellable, is_purchasable 등)
|
||||
* - Materials 전용 필드 (is_inspection 등)
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('item_details', function (Blueprint $table) {
|
||||
$table->id()->comment('ID');
|
||||
$table->foreignId('item_id')->comment('품목 ID');
|
||||
|
||||
// Products 전용 필드
|
||||
$table->boolean('is_sellable')->default(true)->comment('판매 가능');
|
||||
$table->boolean('is_purchasable')->default(false)->comment('구매 가능');
|
||||
$table->boolean('is_producible')->default(false)->comment('생산 가능');
|
||||
$table->integer('safety_stock')->nullable()->comment('안전 재고');
|
||||
$table->integer('lead_time')->nullable()->comment('리드타임(일)');
|
||||
$table->boolean('is_variable_size')->default(false)->comment('가변 크기 여부');
|
||||
$table->string('product_category', 50)->nullable()->comment('제품 카테고리');
|
||||
$table->string('part_type', 50)->nullable()->comment('부품 타입');
|
||||
|
||||
// 파일 필드 (Products)
|
||||
$table->string('bending_diagram')->nullable()->comment('벤딩 도면');
|
||||
$table->json('bending_details')->nullable()->comment('벤딩 상세');
|
||||
$table->string('specification_file')->nullable()->comment('규격서 파일');
|
||||
$table->string('specification_file_name')->nullable()->comment('규격서 파일명');
|
||||
$table->string('certification_file')->nullable()->comment('인증서 파일');
|
||||
$table->string('certification_file_name')->nullable()->comment('인증서 파일명');
|
||||
$table->string('certification_number')->nullable()->comment('인증 번호');
|
||||
$table->date('certification_start_date')->nullable()->comment('인증 시작일');
|
||||
$table->date('certification_end_date')->nullable()->comment('인증 종료일');
|
||||
|
||||
// Materials 전용 필드
|
||||
$table->string('is_inspection', 1)->default('N')->comment('검사 여부');
|
||||
$table->string('item_name')->nullable()->comment('품명');
|
||||
$table->string('specification')->nullable()->comment('규격');
|
||||
$table->text('search_tag')->nullable()->comment('검색 태그');
|
||||
$table->text('remarks')->nullable()->comment('비고');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// 유니크 제약
|
||||
$table->unique('item_id', 'uq_item_details_item_id');
|
||||
|
||||
// 외래키
|
||||
$table->foreign('item_id')->references('id')->on('items')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('item_details');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1.3: 데이터 이관
|
||||
*
|
||||
* products (FG, PT) + materials (SM, RM, CS) → items + item_details
|
||||
*
|
||||
* ID 매핑 전략:
|
||||
* - item_id_mappings 테이블로 기존 ID → 새 ID 매핑 유지
|
||||
* - 참조 테이블 업데이트 시 사용 (Phase 5)
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// ID 매핑 테이블 생성
|
||||
Schema::create('item_id_mappings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('source_table', 20)->comment('products or materials');
|
||||
$table->unsignedBigInteger('source_id')->comment('원본 테이블 ID');
|
||||
$table->unsignedBigInteger('item_id')->comment('items 테이블 ID');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['source_table', 'source_id']);
|
||||
$table->index('item_id');
|
||||
});
|
||||
|
||||
$productCount = 0;
|
||||
$materialCount = 0;
|
||||
|
||||
// 1. Products → Items + ItemDetails
|
||||
$products = DB::table('products')
|
||||
->whereIn('product_type', ['FG', 'PT'])
|
||||
->get();
|
||||
|
||||
foreach ($products as $product) {
|
||||
// items 테이블에 삽입
|
||||
$itemId = DB::table('items')->insertGetId([
|
||||
'tenant_id' => $product->tenant_id,
|
||||
'item_type' => $product->product_type,
|
||||
'code' => $product->code,
|
||||
'name' => $product->name,
|
||||
'unit' => $product->unit,
|
||||
'category_id' => $product->category_id,
|
||||
'bom' => $product->bom,
|
||||
'attributes' => $product->attributes,
|
||||
'attributes_archive' => $product->attributes_archive ?? null,
|
||||
'options' => $product->options,
|
||||
'description' => $product->description,
|
||||
'is_active' => $product->is_active,
|
||||
'created_by' => $product->created_by,
|
||||
'updated_by' => $product->updated_by,
|
||||
'deleted_by' => $product->deleted_by,
|
||||
'created_at' => $product->created_at,
|
||||
'updated_at' => $product->updated_at,
|
||||
'deleted_at' => $product->deleted_at,
|
||||
]);
|
||||
|
||||
// ID 매핑 저장
|
||||
DB::table('item_id_mappings')->insert([
|
||||
'source_table' => 'products',
|
||||
'source_id' => $product->id,
|
||||
'item_id' => $itemId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// item_details 테이블에 삽입
|
||||
DB::table('item_details')->insert([
|
||||
'item_id' => $itemId,
|
||||
'is_sellable' => $product->is_sellable ?? true,
|
||||
'is_purchasable' => $product->is_purchasable ?? false,
|
||||
'is_producible' => $product->is_producible ?? false,
|
||||
'safety_stock' => $product->safety_stock,
|
||||
'lead_time' => $product->lead_time,
|
||||
'is_variable_size' => $product->is_variable_size ?? false,
|
||||
'product_category' => $product->product_category,
|
||||
'part_type' => $product->part_type,
|
||||
'bending_diagram' => $product->bending_diagram,
|
||||
'bending_details' => $product->bending_details,
|
||||
'specification_file' => $product->specification_file,
|
||||
'specification_file_name' => $product->specification_file_name,
|
||||
'certification_file' => $product->certification_file,
|
||||
'certification_file_name' => $product->certification_file_name,
|
||||
'certification_number' => $product->certification_number,
|
||||
'certification_start_date' => $product->certification_start_date,
|
||||
'certification_end_date' => $product->certification_end_date,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$productCount++;
|
||||
}
|
||||
|
||||
// 2. Materials → Items + ItemDetails
|
||||
$materials = DB::table('materials')
|
||||
->whereIn('material_type', ['SM', 'RM', 'CS'])
|
||||
->get();
|
||||
|
||||
foreach ($materials as $material) {
|
||||
// items 테이블에 삽입
|
||||
$itemId = DB::table('items')->insertGetId([
|
||||
'tenant_id' => $material->tenant_id,
|
||||
'item_type' => $material->material_type,
|
||||
'code' => $material->material_code,
|
||||
'name' => $material->name,
|
||||
'unit' => $material->unit,
|
||||
'category_id' => $material->category_id,
|
||||
'bom' => null,
|
||||
'attributes' => $material->attributes,
|
||||
'attributes_archive' => null,
|
||||
'options' => $material->options,
|
||||
'description' => null,
|
||||
'is_active' => $material->is_active,
|
||||
'created_by' => $material->created_by,
|
||||
'updated_by' => $material->updated_by,
|
||||
'deleted_by' => $material->deleted_by,
|
||||
'created_at' => $material->created_at,
|
||||
'updated_at' => $material->updated_at,
|
||||
'deleted_at' => $material->deleted_at,
|
||||
]);
|
||||
|
||||
// ID 매핑 저장
|
||||
DB::table('item_id_mappings')->insert([
|
||||
'source_table' => 'materials',
|
||||
'source_id' => $material->id,
|
||||
'item_id' => $itemId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// item_details 테이블에 삽입
|
||||
DB::table('item_details')->insert([
|
||||
'item_id' => $itemId,
|
||||
'is_inspection' => $material->is_inspection ?? 'N',
|
||||
'item_name' => $material->item_name,
|
||||
'specification' => $material->specification,
|
||||
'search_tag' => $material->search_tag,
|
||||
'remarks' => $material->remarks,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$materialCount++;
|
||||
}
|
||||
|
||||
Log::info("Phase 1.3: Data migration complete", [
|
||||
'products_migrated' => $productCount,
|
||||
'materials_migrated' => $materialCount,
|
||||
'total' => $productCount + $materialCount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// items와 item_details 데이터 삭제
|
||||
DB::table('item_details')->truncate();
|
||||
DB::table('items')->truncate();
|
||||
|
||||
// ID 매핑 테이블 삭제
|
||||
Schema::dropIfExists('item_id_mappings');
|
||||
|
||||
Log::info("Phase 1.3: Data migration rolled back");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* item_pages.source_table을 'products'/'materials'에서 'items'로 통합
|
||||
* - item_type 컬럼은 유지 (FG, PT, SM, RM, CS로 구분)
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// products -> items 업데이트
|
||||
$productsUpdated = DB::table('item_pages')
|
||||
->where('source_table', 'products')
|
||||
->update(['source_table' => 'items']);
|
||||
|
||||
// materials -> items 업데이트
|
||||
$materialsUpdated = DB::table('item_pages')
|
||||
->where('source_table', 'materials')
|
||||
->update(['source_table' => 'items']);
|
||||
|
||||
// 로그 출력
|
||||
if ($productsUpdated > 0 || $materialsUpdated > 0) {
|
||||
echo "Updated item_pages.source_table: products({$productsUpdated}), materials({$materialsUpdated}) -> items\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* item_type 기준으로 source_table 복원
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Product 타입 (FG, PT) -> products
|
||||
DB::table('item_pages')
|
||||
->where('source_table', 'items')
|
||||
->whereIn('item_type', ['FG', 'PT'])
|
||||
->update(['source_table' => 'products']);
|
||||
|
||||
// Material 타입 (SM, RM, CS) -> materials
|
||||
DB::table('item_pages')
|
||||
->where('source_table', 'items')
|
||||
->whereIn('item_type', ['SM', 'RM', 'CS'])
|
||||
->update(['source_table' => 'materials']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 5: 참조 테이블 마이그레이션
|
||||
*
|
||||
* 기존 products/materials 참조를 items 참조로 변경
|
||||
* item_id_mappings 테이블을 활용하여 ID 변환
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 1. product_components: ref_type + ref_id → item_id
|
||||
$this->migrateProductComponents();
|
||||
|
||||
// 2. bom_template_items: ref_type + ref_id → item_id
|
||||
$this->migrateBomTemplateItems();
|
||||
|
||||
// 3. orders: product_id → item_id
|
||||
$this->migrateOrders();
|
||||
|
||||
// 4. order_items: product_id → item_id
|
||||
$this->migrateOrderItems();
|
||||
|
||||
// 5. material_receipts: material_id → item_id
|
||||
$this->migrateMaterialReceipts();
|
||||
|
||||
// 6. lots: material_id → item_id
|
||||
$this->migrateLots();
|
||||
|
||||
// 7. price_histories: item_type + item_id → item_id
|
||||
$this->migratePriceHistories();
|
||||
|
||||
// 8. item_fields: source_table 업데이트
|
||||
$this->migrateItemFields();
|
||||
|
||||
Log::info('Phase 5: Reference tables migration complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* product_components 마이그레이션
|
||||
*/
|
||||
private function migrateProductComponents(): void
|
||||
{
|
||||
if (! Schema::hasTable('product_components')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('product_components', 'item_id')) {
|
||||
Schema::table('product_components', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('ref_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// parent_item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('product_components', 'parent_item_id')) {
|
||||
Schema::table('product_components', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('parent_item_id')->nullable()->after('parent_product_id')
|
||||
->comment('상위 품목 ID (items 참조)');
|
||||
});
|
||||
}
|
||||
|
||||
// ref_type=PRODUCT → products 매핑으로 item_id 업데이트
|
||||
DB::statement("
|
||||
UPDATE product_components pc
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = pc.ref_id
|
||||
SET pc.item_id = m.item_id
|
||||
WHERE pc.ref_type = 'PRODUCT'
|
||||
");
|
||||
|
||||
// ref_type=MATERIAL → materials 매핑으로 item_id 업데이트
|
||||
DB::statement("
|
||||
UPDATE product_components pc
|
||||
JOIN item_id_mappings m ON m.source_table = 'materials' AND m.source_id = pc.ref_id
|
||||
SET pc.item_id = m.item_id
|
||||
WHERE pc.ref_type = 'MATERIAL'
|
||||
");
|
||||
|
||||
// parent_product_id → parent_item_id 업데이트
|
||||
DB::statement("
|
||||
UPDATE product_components pc
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = pc.parent_product_id
|
||||
SET pc.parent_item_id = m.item_id
|
||||
WHERE pc.parent_product_id IS NOT NULL
|
||||
");
|
||||
|
||||
Log::info('Phase 5: product_components migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* bom_template_items 마이그레이션
|
||||
*/
|
||||
private function migrateBomTemplateItems(): void
|
||||
{
|
||||
if (! Schema::hasTable('bom_template_items')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('bom_template_items', 'item_id')) {
|
||||
Schema::table('bom_template_items', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('ref_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// ref_type=PRODUCT → products 매핑
|
||||
DB::statement("
|
||||
UPDATE bom_template_items bti
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = bti.ref_id
|
||||
SET bti.item_id = m.item_id
|
||||
WHERE bti.ref_type = 'PRODUCT'
|
||||
");
|
||||
|
||||
// ref_type=MATERIAL → materials 매핑
|
||||
DB::statement("
|
||||
UPDATE bom_template_items bti
|
||||
JOIN item_id_mappings m ON m.source_table = 'materials' AND m.source_id = bti.ref_id
|
||||
SET bti.item_id = m.item_id
|
||||
WHERE bti.ref_type = 'MATERIAL'
|
||||
");
|
||||
|
||||
Log::info('Phase 5: bom_template_items migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* orders 마이그레이션
|
||||
*/
|
||||
private function migrateOrders(): void
|
||||
{
|
||||
if (! Schema::hasTable('orders')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('orders', 'item_id')) {
|
||||
Schema::table('orders', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('product_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// product_id → item_id 매핑
|
||||
DB::statement("
|
||||
UPDATE orders o
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = o.product_id
|
||||
SET o.item_id = m.item_id
|
||||
WHERE o.product_id IS NOT NULL
|
||||
");
|
||||
|
||||
Log::info('Phase 5: orders migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* order_items 마이그레이션
|
||||
*/
|
||||
private function migrateOrderItems(): void
|
||||
{
|
||||
if (! Schema::hasTable('order_items')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('order_items', 'item_id')) {
|
||||
Schema::table('order_items', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('product_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// product_id → item_id 매핑
|
||||
DB::statement("
|
||||
UPDATE order_items oi
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = oi.product_id
|
||||
SET oi.item_id = m.item_id
|
||||
WHERE oi.product_id IS NOT NULL
|
||||
");
|
||||
|
||||
Log::info('Phase 5: order_items migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* material_receipts 마이그레이션
|
||||
*/
|
||||
private function migrateMaterialReceipts(): void
|
||||
{
|
||||
if (! Schema::hasTable('material_receipts')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('material_receipts', 'item_id')) {
|
||||
Schema::table('material_receipts', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('material_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// material_id → item_id 매핑
|
||||
DB::statement("
|
||||
UPDATE material_receipts mr
|
||||
JOIN item_id_mappings m ON m.source_table = 'materials' AND m.source_id = mr.material_id
|
||||
SET mr.item_id = m.item_id
|
||||
WHERE mr.material_id IS NOT NULL
|
||||
");
|
||||
|
||||
Log::info('Phase 5: material_receipts migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* lots 마이그레이션
|
||||
*/
|
||||
private function migrateLots(): void
|
||||
{
|
||||
if (! Schema::hasTable('lots')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item_id 컬럼 추가
|
||||
if (! Schema::hasColumn('lots', 'item_id')) {
|
||||
Schema::table('lots', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('item_id')->nullable()->after('material_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// material_id → item_id 매핑
|
||||
DB::statement("
|
||||
UPDATE lots l
|
||||
JOIN item_id_mappings m ON m.source_table = 'materials' AND m.source_id = l.material_id
|
||||
SET l.item_id = m.item_id
|
||||
WHERE l.material_id IS NOT NULL
|
||||
");
|
||||
|
||||
Log::info('Phase 5: lots migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* price_histories 마이그레이션
|
||||
*/
|
||||
private function migratePriceHistories(): void
|
||||
{
|
||||
if (! Schema::hasTable('price_histories')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// new_item_id 컬럼 추가 (item_id가 이미 있을 수 있음)
|
||||
if (! Schema::hasColumn('price_histories', 'new_item_id')) {
|
||||
Schema::table('price_histories', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('new_item_id')->nullable()->after('item_id')
|
||||
->comment('items 테이블 참조 ID');
|
||||
});
|
||||
}
|
||||
|
||||
// item_type=PRODUCT → products 매핑
|
||||
DB::statement("
|
||||
UPDATE price_histories ph
|
||||
JOIN item_id_mappings m ON m.source_table = 'products' AND m.source_id = ph.item_id
|
||||
SET ph.new_item_id = m.item_id
|
||||
WHERE ph.item_type = 'PRODUCT'
|
||||
");
|
||||
|
||||
// item_type=MATERIAL → materials 매핑
|
||||
DB::statement("
|
||||
UPDATE price_histories ph
|
||||
JOIN item_id_mappings m ON m.source_table = 'materials' AND m.source_id = ph.item_id
|
||||
SET ph.new_item_id = m.item_id
|
||||
WHERE ph.item_type = 'MATERIAL'
|
||||
");
|
||||
|
||||
Log::info('Phase 5: price_histories migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* item_fields source_table 업데이트
|
||||
*/
|
||||
private function migrateItemFields(): void
|
||||
{
|
||||
if (! Schema::hasTable('item_fields')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// source_table 컬럼 값 업데이트
|
||||
DB::table('item_fields')
|
||||
->whereIn('source_table', ['products', 'materials'])
|
||||
->update(['source_table' => 'items']);
|
||||
|
||||
Log::info('Phase 5: item_fields migrated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// item_fields 복원
|
||||
// Note: 원본 source_table 값은 item_type으로 추론
|
||||
DB::statement("
|
||||
UPDATE item_fields if2
|
||||
SET if2.source_table = CASE
|
||||
WHEN if2.item_type IN ('FG', 'PT') THEN 'products'
|
||||
WHEN if2.item_type IN ('SM', 'RM', 'CS') THEN 'materials'
|
||||
ELSE if2.source_table
|
||||
END
|
||||
WHERE if2.source_table = 'items'
|
||||
");
|
||||
|
||||
// 추가된 컬럼 제거
|
||||
$columnsToRemove = [
|
||||
'product_components' => ['item_id', 'parent_item_id'],
|
||||
'bom_template_items' => ['item_id'],
|
||||
'orders' => ['item_id'],
|
||||
'order_items' => ['item_id'],
|
||||
'material_receipts' => ['item_id'],
|
||||
'lots' => ['item_id'],
|
||||
'price_histories' => ['new_item_id'],
|
||||
];
|
||||
|
||||
foreach ($columnsToRemove as $table => $columns) {
|
||||
if (Schema::hasTable($table)) {
|
||||
Schema::table($table, function (Blueprint $table) use ($columns) {
|
||||
foreach ($columns as $column) {
|
||||
if (Schema::hasColumn($table->getTable(), $column)) {
|
||||
$table->dropColumn($column);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('Phase 5: Reference tables migration rolled back');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user