feat(item-master): 잠금 기능 추가 및 FK 레거시 코드 정리

## 잠금 기능 (Lock Feature)
- entity_relationships 테이블에 is_locked, locked_by, locked_at 컬럼 추가
- EntityRelationship 모델에 잠금 관련 헬퍼 메서드 추가
- LockCheckTrait 생성 (destroy 시 잠금 체크 공통 로직)
- 각 Service의 destroy() 메서드에 잠금 체크 적용
- API 응답에 is_locked 필드 포함
- 한국어 에러 메시지 추가

## FK 레거시 코드 정리
- ItemMasterSeeder: entity_relationships 기반으로 전환
- ItemPage 모델: FK 기반 sections() 관계 제거
- ItemSectionService: clone() 메서드 FK 제거
- SectionTemplateService: page_id 컬럼 참조 제거
- EntityRelationship::link() 파라미터 순서 통일

## 기타
- Swagger 스키마에 is_locked 속성 추가
- 프론트엔드 가이드 문서 추가
This commit is contained in:
2025-11-27 15:51:00 +09:00
parent 29fe1415e5
commit efa2a84d2c
35 changed files with 537 additions and 142 deletions

View File

@@ -39,4 +39,4 @@ public function down(): void
{
Schema::dropIfExists('unit_options');
}
};
};

View File

@@ -41,4 +41,4 @@ public function down(): void
{
Schema::dropIfExists('section_templates');
}
};
};

View File

@@ -47,4 +47,4 @@ public function down(): void
{
Schema::dropIfExists('item_master_fields');
}
};
};

View File

@@ -42,4 +42,4 @@ public function down(): void
{
Schema::dropIfExists('item_pages');
}
};
};

View File

@@ -45,4 +45,4 @@ public function down(): void
{
Schema::dropIfExists('item_sections');
}
};
};

View File

@@ -52,4 +52,4 @@ public function down(): void
{
Schema::dropIfExists('item_fields');
}
};
};

View File

@@ -49,4 +49,4 @@ public function down(): void
{
Schema::dropIfExists('item_bom_items');
}
};
};

View File

@@ -42,4 +42,4 @@ public function down(): void
{
Schema::dropIfExists('custom_tabs');
}
};
};

View File

@@ -40,4 +40,4 @@ public function down(): void
{
Schema::dropIfExists('tab_columns');
}
};
};

View File

@@ -65,13 +65,13 @@ public function up(): void
];
foreach ($tables as $tableName) {
if (!Schema::hasTable($tableName) || in_array($tableName, $excludeTables)) {
if (! Schema::hasTable($tableName) || in_array($tableName, $excludeTables)) {
continue;
}
Schema::table($tableName, function (Blueprint $table) use ($tableName) {
// created_by 추가
if (!Schema::hasColumn($tableName, 'created_by')) {
if (! Schema::hasColumn($tableName, 'created_by')) {
// updated_at 다음에 추가
if (Schema::hasColumn($tableName, 'updated_at')) {
$table->foreignId('created_by')
@@ -86,7 +86,7 @@ public function up(): void
}
// updated_by 추가
if (!Schema::hasColumn($tableName, 'updated_by')) {
if (! Schema::hasColumn($tableName, 'updated_by')) {
// created_by 다음에 추가
if (Schema::hasColumn($tableName, 'created_by')) {
$table->foreignId('updated_by')
@@ -159,7 +159,7 @@ public function down(): void
];
foreach ($tables as $tableName) {
if (!Schema::hasTable($tableName) || in_array($tableName, $excludeTables)) {
if (! Schema::hasTable($tableName) || in_array($tableName, $excludeTables)) {
continue;
}
@@ -176,4 +176,4 @@ public function down(): void
});
}
}
};
};

View File

@@ -47,12 +47,12 @@ public function up(): void
];
foreach ($tables as $tableName) {
if (!Schema::hasTable($tableName)) {
if (! Schema::hasTable($tableName)) {
continue;
}
Schema::table($tableName, function (Blueprint $table) use ($tableName) {
if (!Schema::hasColumn($tableName, 'deleted_by')) {
if (! Schema::hasColumn($tableName, 'deleted_by')) {
$table->foreignId('deleted_by')
->nullable()
->after('deleted_at')
@@ -101,7 +101,7 @@ public function down(): void
];
foreach ($tables as $tableName) {
if (!Schema::hasTable($tableName)) {
if (! Schema::hasTable($tableName)) {
continue;
}
@@ -112,4 +112,4 @@ public function down(): void
});
}
}
};
};

View File

@@ -31,4 +31,4 @@ public function down(): void
$table->unsignedBigInteger('page_id')->nullable(false)->comment('페이지 ID')->change();
});
}
};
};

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* 연결(관계) 잠금 기능 추가:
* - is_locked=true인 연결은 해제 불가
* - 잠금된 연결의 자식 엔티티도 삭제 불가 (연결을 통한 보호)
*/
public function up(): void
{
Schema::table('entity_relationships', function (Blueprint $table) {
$table->boolean('is_locked')->default(false)->after('metadata')->comment('잠금 여부 (잠금 시 연결 해제 및 자식 삭제 불가)');
$table->unsignedBigInteger('locked_by')->nullable()->after('is_locked')->comment('잠금 설정자 ID');
$table->timestamp('locked_at')->nullable()->after('locked_by')->comment('잠금 설정 일시');
$table->index('is_locked', 'idx_entity_rel_is_locked');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('entity_relationships', function (Blueprint $table) {
$table->dropIndex('idx_entity_rel_is_locked');
$table->dropColumn(['is_locked', 'locked_by', 'locked_at']);
});
}
};