From af42c115ae0e8b0fbeed0863feacc189c6d64736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Wed, 4 Feb 2026 08:37:55 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EA=B2=80=EC=82=AC=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=84=9C=20=EB=8F=99=EC=A0=81=ED=99=94=20+=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=ED=82=A4=20=EB=A7=A4=ED=95=91=20=EB=8F=99=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 템플릿별 동적 필드 정의 (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 --- app/Models/Documents/DocumentLink.php | 38 ++++ app/Models/Documents/DocumentTemplate.php | 18 ++ .../Documents/DocumentTemplateFieldPreset.php | 27 +++ app/Models/Documents/DocumentTemplateLink.php | 45 +++++ .../Documents/DocumentTemplateLinkValue.php | 38 ++++ .../DocumentTemplateSectionField.php | 36 ++++ .../Documents/DocumentTemplateSectionItem.php | 21 ++ ...document_template_section_fields_table.php | 32 +++ ..._create_document_template_links_tables.php | 59 ++++++ ..._document_template_field_presets_table.php | 26 +++ ...0300_add_field_values_to_section_items.php | 23 +++ ...00400_migrate_section_items_to_dynamic.php | 191 ++++++++++++++++++ ...plate_links_search_api_to_source_table.php | 61 ++++++ .../DocumentTemplateFieldPresetSeeder.php | 148 ++++++++++++++ 14 files changed, 763 insertions(+) create mode 100644 app/Models/Documents/DocumentLink.php create mode 100644 app/Models/Documents/DocumentTemplateFieldPreset.php create mode 100644 app/Models/Documents/DocumentTemplateLink.php create mode 100644 app/Models/Documents/DocumentTemplateLinkValue.php create mode 100644 app/Models/Documents/DocumentTemplateSectionField.php create mode 100644 database/migrations/2026_02_03_200000_create_document_template_section_fields_table.php create mode 100644 database/migrations/2026_02_03_200100_create_document_template_links_tables.php create mode 100644 database/migrations/2026_02_03_200200_create_document_template_field_presets_table.php create mode 100644 database/migrations/2026_02_03_200300_add_field_values_to_section_items.php create mode 100644 database/migrations/2026_02_03_200400_migrate_section_items_to_dynamic.php create mode 100644 database/migrations/2026_02_03_210000_change_template_links_search_api_to_source_table.php create mode 100644 database/seeders/DocumentTemplateFieldPresetSeeder.php diff --git a/app/Models/Documents/DocumentLink.php b/app/Models/Documents/DocumentLink.php new file mode 100644 index 0000000..a73a06d --- /dev/null +++ b/app/Models/Documents/DocumentLink.php @@ -0,0 +1,38 @@ + 'integer', + ]; + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class, 'document_id'); + } + + public function linkDefinition(): BelongsTo + { + return $this->belongsTo(DocumentTemplateLink::class, 'link_id'); + } +} diff --git a/app/Models/Documents/DocumentTemplate.php b/app/Models/Documents/DocumentTemplate.php index a1dd054..6b4980a 100644 --- a/app/Models/Documents/DocumentTemplate.php +++ b/app/Models/Documents/DocumentTemplate.php @@ -91,6 +91,24 @@ public function columns(): HasMany ->orderBy('sort_order'); } + /** + * 검사 기준서 동적 필드 정의 + */ + public function sectionFields(): HasMany + { + return $this->hasMany(DocumentTemplateSectionField::class, 'template_id') + ->orderBy('sort_order'); + } + + /** + * 외부 키 매핑 정의 + */ + public function links(): HasMany + { + return $this->hasMany(DocumentTemplateLink::class, 'template_id') + ->orderBy('sort_order'); + } + // ========================================================================= // Scopes // ========================================================================= diff --git a/app/Models/Documents/DocumentTemplateFieldPreset.php b/app/Models/Documents/DocumentTemplateFieldPreset.php new file mode 100644 index 0000000..dd76bc3 --- /dev/null +++ b/app/Models/Documents/DocumentTemplateFieldPreset.php @@ -0,0 +1,27 @@ + 'array', + 'links' => 'array', + 'sort_order' => 'integer', + ]; +} diff --git a/app/Models/Documents/DocumentTemplateLink.php b/app/Models/Documents/DocumentTemplateLink.php new file mode 100644 index 0000000..e7d3c00 --- /dev/null +++ b/app/Models/Documents/DocumentTemplateLink.php @@ -0,0 +1,45 @@ + 'array', + 'display_fields' => 'array', + 'is_required' => 'boolean', + 'sort_order' => 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } + + public function linkValues(): HasMany + { + return $this->hasMany(DocumentTemplateLinkValue::class, 'link_id') + ->orderBy('sort_order'); + } +} diff --git a/app/Models/Documents/DocumentTemplateLinkValue.php b/app/Models/Documents/DocumentTemplateLinkValue.php new file mode 100644 index 0000000..f9033b8 --- /dev/null +++ b/app/Models/Documents/DocumentTemplateLinkValue.php @@ -0,0 +1,38 @@ + 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } + + public function link(): BelongsTo + { + return $this->belongsTo(DocumentTemplateLink::class, 'link_id'); + } +} diff --git a/app/Models/Documents/DocumentTemplateSectionField.php b/app/Models/Documents/DocumentTemplateSectionField.php new file mode 100644 index 0000000..ff67920 --- /dev/null +++ b/app/Models/Documents/DocumentTemplateSectionField.php @@ -0,0 +1,36 @@ + 'array', + 'is_required' => 'boolean', + 'sort_order' => 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } +} diff --git a/app/Models/Documents/DocumentTemplateSectionItem.php b/app/Models/Documents/DocumentTemplateSectionItem.php index 5690621..7ac0b23 100644 --- a/app/Models/Documents/DocumentTemplateSectionItem.php +++ b/app/Models/Documents/DocumentTemplateSectionItem.php @@ -28,17 +28,38 @@ class DocumentTemplateSectionItem extends Model 'item', 'standard', 'tolerance', + 'standard_criteria', 'method', 'measurement_type', + 'frequency_n', + 'frequency_c', 'frequency', 'regulation', + 'field_values', 'sort_order', ]; protected $casts = [ + 'tolerance' => 'array', + 'standard_criteria' => 'array', + 'field_values' => 'array', 'sort_order' => 'integer', + 'frequency_n' => 'integer', + 'frequency_c' => 'integer', ]; + /** + * field_values 우선, 없으면 기존 컬럼 fallback + */ + public function getFieldValue(string $key): mixed + { + if (! empty($this->field_values) && array_key_exists($key, $this->field_values)) { + return $this->field_values[$key]; + } + + return $this->attributes[$key] ?? null; + } + public function section(): BelongsTo { return $this->belongsTo(DocumentTemplateSection::class, 'section_id'); diff --git a/database/migrations/2026_02_03_200000_create_document_template_section_fields_table.php b/database/migrations/2026_02_03_200000_create_document_template_section_fields_table.php new file mode 100644 index 0000000..bcd6f3a --- /dev/null +++ b/database/migrations/2026_02_03_200000_create_document_template_section_fields_table.php @@ -0,0 +1,32 @@ +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'); + } +}; diff --git a/database/migrations/2026_02_03_200100_create_document_template_links_tables.php b/database/migrations/2026_02_03_200100_create_document_template_links_tables.php new file mode 100644 index 0000000..796259c --- /dev/null +++ b/database/migrations/2026_02_03_200100_create_document_template_links_tables.php @@ -0,0 +1,59 @@ +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'); + } +}; diff --git a/database/migrations/2026_02_03_200200_create_document_template_field_presets_table.php b/database/migrations/2026_02_03_200200_create_document_template_field_presets_table.php new file mode 100644 index 0000000..f74c5e9 --- /dev/null +++ b/database/migrations/2026_02_03_200200_create_document_template_field_presets_table.php @@ -0,0 +1,26 @@ +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'); + } +}; diff --git a/database/migrations/2026_02_03_200300_add_field_values_to_section_items.php b/database/migrations/2026_02_03_200300_add_field_values_to_section_items.php new file mode 100644 index 0000000..5d0afa7 --- /dev/null +++ b/database/migrations/2026_02_03_200300_add_field_values_to_section_items.php @@ -0,0 +1,23 @@ +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'); + }); + } +}; diff --git a/database/migrations/2026_02_03_200400_migrate_section_items_to_dynamic.php b/database/migrations/2026_02_03_200400_migrate_section_items_to_dynamic.php new file mode 100644 index 0000000..996c4f0 --- /dev/null +++ b/database/migrations/2026_02_03_200400_migrate_section_items_to_dynamic.php @@ -0,0 +1,191 @@ + '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]); + } +}; diff --git a/database/migrations/2026_02_03_210000_change_template_links_search_api_to_source_table.php b/database/migrations/2026_02_03_210000_change_template_links_search_api_to_source_table.php new file mode 100644 index 0000000..061a063 --- /dev/null +++ b/database/migrations/2026_02_03_210000_change_template_links_search_api_to_source_table.php @@ -0,0 +1,61 @@ +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'); + }); + } +}; diff --git a/database/seeders/DocumentTemplateFieldPresetSeeder.php b/database/seeders/DocumentTemplateFieldPresetSeeder.php new file mode 100644 index 0000000..97ab726 --- /dev/null +++ b/database/seeders/DocumentTemplateFieldPresetSeeder.php @@ -0,0 +1,148 @@ +truncate(); + + // 수입검사 프리셋 + DB::table('document_template_field_presets')->insert([ + 'name' => '수입검사 기본 필드 세트', + 'category' => '수입검사', + 'fields' => json_encode([ + ['field_key' => 'category', 'label' => '구분', 'field_type' => 'text', 'width' => '65px', 'is_required' => false], + ['field_key' => 'item', 'label' => '검사항목', 'field_type' => 'text', 'width' => '130px', 'is_required' => true], + ['field_key' => 'standard', 'label' => '검사기준', 'field_type' => 'text', 'width' => '180px', 'is_required' => false], + ['field_key' => 'standard_criteria', 'label' => '기준범위', 'field_type' => 'json_criteria', 'width' => '100px', 'is_required' => false], + ['field_key' => 'tolerance', 'label' => '공차/범위', 'field_type' => 'json_tolerance', 'width' => '120px', 'is_required' => false], + ['field_key' => 'method', 'label' => '검사방식', 'field_type' => 'select_api', 'width' => '110px', 'is_required' => false, 'options' => [ + '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, 'options' => [ + '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], + ['field_key' => 'regulation', 'label' => '관련규정', 'field_type' => 'text', 'width' => '80px', 'is_required' => false], + ], JSON_UNESCAPED_UNICODE), + 'links' => json_encode([ + ['link_key' => 'items', 'label' => '연결 품목 (RM, SM)', 'link_type' => 'multiple', 'source_table' => 'items', 'search_params' => ['item_type' => 'RM,SM'], 'display_fields' => ['title' => 'name', 'subtitle' => 'code']], + ], JSON_UNESCAPED_UNICODE), + 'sort_order' => 0, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + // 품질검사 프리셋 + DB::table('document_template_field_presets')->insert([ + 'name' => '품질검사 기본 필드 세트', + 'category' => '품질검사', + 'fields' => json_encode([ + ['field_key' => 'category', 'label' => '구분', 'field_type' => 'text', 'width' => '65px', 'is_required' => false], + ['field_key' => 'item', 'label' => '검사항목', 'field_type' => 'text', 'width' => '130px', 'is_required' => true], + ['field_key' => 'standard', 'label' => '검사기준', 'field_type' => 'text', 'width' => '180px', 'is_required' => false], + ['field_key' => 'standard_criteria', 'label' => '기준범위', 'field_type' => 'json_criteria', 'width' => '100px', 'is_required' => false], + ['field_key' => 'tolerance', 'label' => '공차/범위', 'field_type' => 'json_tolerance', 'width' => '120px', 'is_required' => false], + ['field_key' => 'method', 'label' => '검사방식', 'field_type' => 'select_api', 'width' => '110px', 'is_required' => false, 'options' => [ + '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, 'options' => [ + '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], + ['field_key' => 'regulation', 'label' => '관련규정', 'field_type' => 'text', 'width' => '80px', 'is_required' => false], + ], JSON_UNESCAPED_UNICODE), + 'links' => json_encode([ + ['link_key' => 'process', 'label' => '연결 공정', 'link_type' => 'single', 'source_table' => 'processes', 'search_params' => null, 'display_fields' => ['title' => 'process_name', 'subtitle' => 'process_code']], + ], JSON_UNESCAPED_UNICODE), + 'sort_order' => 1, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + // 출하검사 프리셋 + DB::table('document_template_field_presets')->insert([ + 'name' => '출하검사 기본 필드 세트', + 'category' => '출하검사', + 'fields' => json_encode([ + ['field_key' => 'category', 'label' => '구분', 'field_type' => 'text', 'width' => '65px', 'is_required' => false], + ['field_key' => 'item', 'label' => '검사항목', 'field_type' => 'text', 'width' => '130px', 'is_required' => true], + ['field_key' => 'standard', 'label' => '검사기준', 'field_type' => 'text', 'width' => '180px', 'is_required' => false], + ['field_key' => 'standard_criteria', 'label' => '기준범위', 'field_type' => 'json_criteria', 'width' => '100px', 'is_required' => false], + ['field_key' => 'tolerance', 'label' => '공차/범위', 'field_type' => 'json_tolerance', 'width' => '120px', 'is_required' => false], + ['field_key' => 'method', 'label' => '검사방식', 'field_type' => 'select_api', 'width' => '110px', 'is_required' => false, 'options' => [ + '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, 'options' => [ + '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], + ['field_key' => 'regulation', 'label' => '관련규정', 'field_type' => 'text', 'width' => '80px', 'is_required' => false], + ], JSON_UNESCAPED_UNICODE), + 'links' => json_encode([ + ['link_key' => 'lot', 'label' => '로트번호', 'link_type' => 'single', 'source_table' => 'lots', 'search_params' => null, 'display_fields' => ['title' => 'lot_no', 'subtitle' => 'item_name']], + ], JSON_UNESCAPED_UNICODE), + 'sort_order' => 2, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } +}