feat:검사 기준서 동적화 + 외부 키 매핑 동적화
- 템플릿별 동적 필드 정의 (document_template_section_fields) - 외부 키 매핑 동적화 (document_template_links + link_values) - 문서 레벨 연결 (document_links) - 시스템 프리셋 (document_template_field_presets) - section_items에 field_values JSON 컬럼 추가 - 기존 고정 필드 → 동적 field_values 데이터 마이그레이션 - search_api → source_table 전환 마이그레이션 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('document_template_section_fields', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('field_key', 50)->comment('내부 키 (category, item, standard 등)');
|
||||
$table->string('label', 50)->comment('표시명 (구분, 검사항목 등)');
|
||||
$table->string('field_type', 30)->default('text')
|
||||
->comment('text|number|select|select_api|json_tolerance|json_criteria|composite_frequency');
|
||||
$table->json('options')->nullable()->comment('선택지, API경로 등 부가 설정');
|
||||
$table->string('width', 20)->default('100px')->comment('에디터 UI 컬럼 너비');
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order'], 'idx_template_sort');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_template_section_fields');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 템플릿별 외부 키 매핑 정의
|
||||
Schema::create('document_template_links', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('link_key', 50)->comment('UI 식별 키 (items, process, lot)');
|
||||
$table->string('label', 50)->comment('표시명 (연결 품목, 연결 공정)');
|
||||
$table->string('link_type', 20)->default('single')->comment('single|multiple');
|
||||
$table->string('search_api', 255)->comment('검색 API (/api/admin/items/search)');
|
||||
$table->json('search_params')->nullable()->comment('API 추가 파라미터');
|
||||
$table->json('display_fields')->nullable()->comment('표시 필드 (title, subtitle)');
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order'], 'idx_tpl_link_sort');
|
||||
});
|
||||
|
||||
// 템플릿 레벨 연결 값 (기존 linked_item_ids/linked_process_id 대체)
|
||||
Schema::create('document_template_link_values', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->foreignId('link_id')->constrained('document_template_links')->cascadeOnDelete();
|
||||
$table->unsignedBigInteger('linkable_id')->comment('연결 대상 PK');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['link_id', 'linkable_id'], 'idx_link_value');
|
||||
});
|
||||
|
||||
// 문서 레벨 연결 (기존 linkable_type/linkable_id 대체)
|
||||
Schema::create('document_links', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_id')->constrained('documents')->cascadeOnDelete();
|
||||
$table->unsignedBigInteger('link_id')->comment('FK: document_template_links');
|
||||
$table->unsignedBigInteger('linkable_id');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['document_id', 'link_id'], 'idx_doc_link');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_links');
|
||||
Schema::dropIfExists('document_template_link_values');
|
||||
Schema::dropIfExists('document_template_links');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('document_template_field_presets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name', 100)->comment('프리셋명 (수입검사 기본 필드 세트)');
|
||||
$table->string('category', 50)->nullable()->comment('연관 카테고리');
|
||||
$table->json('fields')->comment('section_fields 배열');
|
||||
$table->json('links')->nullable()->comment('template_links 배열');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_template_field_presets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('document_template_section_items', function (Blueprint $table) {
|
||||
$table->json('field_values')->nullable()->after('regulation')
|
||||
->comment('동적 필드 값 {"field_key": value, ...}');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('document_template_section_items', function (Blueprint $table) {
|
||||
$table->dropColumn('field_values');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 기존 고정 필드 데이터를 동적 구조로 변환
|
||||
*
|
||||
* 1. 각 template에 대해 section_fields 레코드 생성
|
||||
* 2. linked_item_ids → template_links + link_values 변환
|
||||
* 3. linked_process_id → template_links + link_values 변환
|
||||
* 4. 각 section_item의 기존 컬럼값을 field_values JSON으로 변환
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 기본 필드 정의 (기존 고정 컬럼 기반)
|
||||
$defaultFields = [
|
||||
['field_key' => 'category', 'label' => '구분', 'field_type' => 'text', 'width' => '65px', 'is_required' => false, 'sort_order' => 0],
|
||||
['field_key' => 'item', 'label' => '검사항목', 'field_type' => 'text', 'width' => '130px', 'is_required' => true, 'sort_order' => 1],
|
||||
['field_key' => 'standard', 'label' => '검사기준', 'field_type' => 'text', 'width' => '180px', 'is_required' => false, 'sort_order' => 2],
|
||||
['field_key' => 'standard_criteria', 'label' => '기준범위', 'field_type' => 'json_criteria', 'width' => '100px', 'is_required' => false, 'sort_order' => 3],
|
||||
['field_key' => 'tolerance', 'label' => '공차/범위', 'field_type' => 'json_tolerance', 'width' => '120px', 'is_required' => false, 'sort_order' => 4],
|
||||
['field_key' => 'method', 'label' => '검사방식', 'field_type' => 'select_api', 'width' => '110px', 'is_required' => false, 'sort_order' => 5, 'options' => json_encode([
|
||||
'api_endpoint' => '/api/admin/common-codes/inspection_method',
|
||||
'auto_map' => [
|
||||
'target_field' => 'measurement_type',
|
||||
'mapping' => [
|
||||
'visual' => 'checkbox',
|
||||
'check' => 'numeric',
|
||||
'mill_sheet' => 'single_value',
|
||||
'certified_agency' => 'single_value',
|
||||
'substitute_cert' => 'substitute',
|
||||
'other' => 'text',
|
||||
],
|
||||
],
|
||||
])],
|
||||
['field_key' => 'measurement_type', 'label' => '측정유형', 'field_type' => 'select', 'width' => '100px', 'is_required' => false, 'sort_order' => 6, 'options' => json_encode([
|
||||
'choices' => [
|
||||
['code' => 'checkbox', 'name' => 'OK/NG 체크'],
|
||||
['code' => 'numeric', 'name' => '수치입력(3)'],
|
||||
['code' => 'single_value', 'name' => '단일값'],
|
||||
['code' => 'substitute', 'name' => '성적서 대체'],
|
||||
['code' => 'text', 'name' => '자유입력'],
|
||||
],
|
||||
])],
|
||||
['field_key' => 'frequency', 'label' => '검사주기', 'field_type' => 'composite_frequency', 'width' => '120px', 'is_required' => false, 'sort_order' => 7],
|
||||
['field_key' => 'regulation', 'label' => '관련규정', 'field_type' => 'text', 'width' => '80px', 'is_required' => false, 'sort_order' => 8],
|
||||
];
|
||||
|
||||
$now = now();
|
||||
|
||||
// 1. 각 template에 section_fields 생성
|
||||
$templates = DB::table('document_templates')->get();
|
||||
|
||||
foreach ($templates as $template) {
|
||||
// section_fields 생성
|
||||
foreach ($defaultFields as $field) {
|
||||
DB::table('document_template_section_fields')->insert([
|
||||
'template_id' => $template->id,
|
||||
'field_key' => $field['field_key'],
|
||||
'label' => $field['label'],
|
||||
'field_type' => $field['field_type'],
|
||||
'options' => $field['options'] ?? null,
|
||||
'width' => $field['width'],
|
||||
'is_required' => $field['is_required'],
|
||||
'sort_order' => $field['sort_order'],
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
// linked_item_ids → template_links + link_values
|
||||
$linkedItemIds = json_decode($template->linked_item_ids ?? '[]', true);
|
||||
if (! empty($linkedItemIds)) {
|
||||
$linkId = DB::table('document_template_links')->insertGetId([
|
||||
'template_id' => $template->id,
|
||||
'link_key' => 'items',
|
||||
'label' => '연결 품목 (RM, SM)',
|
||||
'link_type' => 'multiple',
|
||||
'search_api' => '/api/admin/items/search',
|
||||
'search_params' => json_encode(['item_type' => 'RM,SM']),
|
||||
'display_fields' => json_encode(['title' => 'name', 'subtitle' => 'code']),
|
||||
'is_required' => false,
|
||||
'sort_order' => 0,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
foreach ($linkedItemIds as $idx => $itemId) {
|
||||
DB::table('document_template_link_values')->insert([
|
||||
'template_id' => $template->id,
|
||||
'link_id' => $linkId,
|
||||
'linkable_id' => $itemId,
|
||||
'sort_order' => $idx,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// linked_process_id → template_links + link_values
|
||||
if (! empty($template->linked_process_id)) {
|
||||
$linkId = DB::table('document_template_links')->insertGetId([
|
||||
'template_id' => $template->id,
|
||||
'link_key' => 'process',
|
||||
'label' => '연결 공정',
|
||||
'link_type' => 'single',
|
||||
'search_api' => '/api/admin/processes/search',
|
||||
'search_params' => null,
|
||||
'display_fields' => json_encode(['title' => 'process_name', 'subtitle' => 'process_code']),
|
||||
'is_required' => false,
|
||||
'sort_order' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
DB::table('document_template_link_values')->insert([
|
||||
'template_id' => $template->id,
|
||||
'link_id' => $linkId,
|
||||
'linkable_id' => $template->linked_process_id,
|
||||
'sort_order' => 0,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 각 section_item의 기존 컬럼값을 field_values JSON으로 변환
|
||||
$items = DB::table('document_template_section_items')->get();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$fieldValues = [];
|
||||
|
||||
if (! empty($item->category)) {
|
||||
$fieldValues['category'] = $item->category;
|
||||
}
|
||||
if (! empty($item->item)) {
|
||||
$fieldValues['item'] = $item->item;
|
||||
}
|
||||
if (! empty($item->standard)) {
|
||||
$fieldValues['standard'] = $item->standard;
|
||||
}
|
||||
if (! empty($item->standard_criteria)) {
|
||||
$decoded = json_decode($item->standard_criteria, true);
|
||||
$fieldValues['standard_criteria'] = $decoded ?? $item->standard_criteria;
|
||||
}
|
||||
if (! empty($item->tolerance)) {
|
||||
$decoded = json_decode($item->tolerance, true);
|
||||
$fieldValues['tolerance'] = $decoded ?? $item->tolerance;
|
||||
}
|
||||
if (! empty($item->method)) {
|
||||
$fieldValues['method'] = $item->method;
|
||||
}
|
||||
if (! empty($item->measurement_type)) {
|
||||
$fieldValues['measurement_type'] = $item->measurement_type;
|
||||
}
|
||||
if (! empty($item->frequency)) {
|
||||
$fieldValues['frequency'] = $item->frequency;
|
||||
}
|
||||
if (! empty($item->frequency_n)) {
|
||||
$fieldValues['frequency_n'] = $item->frequency_n;
|
||||
}
|
||||
if (! empty($item->frequency_c)) {
|
||||
$fieldValues['frequency_c'] = $item->frequency_c;
|
||||
}
|
||||
if (! empty($item->regulation)) {
|
||||
$fieldValues['regulation'] = $item->regulation;
|
||||
}
|
||||
|
||||
if (! empty($fieldValues)) {
|
||||
DB::table('document_template_section_items')
|
||||
->where('id', $item->id)
|
||||
->update(['field_values' => json_encode($fieldValues, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// section_fields 삭제
|
||||
DB::table('document_template_section_fields')->truncate();
|
||||
|
||||
// link_values, links, document_links 삭제
|
||||
DB::table('document_template_link_values')->truncate();
|
||||
DB::table('document_template_links')->truncate();
|
||||
DB::table('document_links')->truncate();
|
||||
|
||||
// field_values NULL로 초기화
|
||||
DB::table('document_template_section_items')->update(['field_values' => null]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 1) source_table 컬럼 추가
|
||||
Schema::table('document_template_links', function (Blueprint $table) {
|
||||
$table->string('source_table', 50)->nullable()->after('link_type')
|
||||
->comment('소스 테이블 키 (system_field_definitions.source_table)');
|
||||
});
|
||||
|
||||
// 2) 기존 search_api 데이터를 source_table로 변환
|
||||
$mapping = [
|
||||
'/api/admin/items/search' => 'items',
|
||||
'/api/admin/processes/search' => 'processes',
|
||||
'/api/admin/lots/search' => 'lots',
|
||||
'/api/admin/tenant-users/search' => 'users',
|
||||
];
|
||||
|
||||
foreach ($mapping as $api => $table) {
|
||||
DB::table('document_template_links')
|
||||
->where('search_api', $api)
|
||||
->update(['source_table' => $table]);
|
||||
}
|
||||
|
||||
// 3) search_api 컬럼 삭제
|
||||
Schema::table('document_template_links', function (Blueprint $table) {
|
||||
$table->dropColumn('search_api');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('document_template_links', function (Blueprint $table) {
|
||||
$table->string('search_api', 255)->after('link_type');
|
||||
});
|
||||
|
||||
$mapping = [
|
||||
'items' => '/api/admin/items/search',
|
||||
'processes' => '/api/admin/processes/search',
|
||||
'lots' => '/api/admin/lots/search',
|
||||
'users' => '/api/admin/tenant-users/search',
|
||||
];
|
||||
|
||||
foreach ($mapping as $table => $api) {
|
||||
DB::table('document_template_links')
|
||||
->where('source_table', $table)
|
||||
->update(['search_api' => $api]);
|
||||
}
|
||||
|
||||
Schema::table('document_template_links', function (Blueprint $table) {
|
||||
$table->dropColumn('source_table');
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user