getTemplateDefinitions(); foreach ($templates as $def) { $this->cleanupExisting($def['name']); $template = DocumentTemplate::create([ 'tenant_id' => $this->tenantId, 'name' => $def['name'], 'category' => '품질/중간검사', 'title' => $def['title'], 'company_name' => '케이디산업', 'footer_remark_label' => '부적합 내용', 'footer_judgement_label' => '종합판정', 'footer_judgement_options' => ['합격', '불합격'], 'is_active' => true, ]); $this->createApprovalLines($template->id); $this->createBasicFields($template->id); foreach ($def['sections'] as $i => $sectionDef) { $this->createSection($template->id, $sectionDef['title'], $sectionDef['items'], $i + 1); } $this->createSectionFields($template->id); $this->createColumns($template->id, $def['columns']); $this->command->info("✅ {$def['name']} (ID: {$template->id})"); } } private function getTemplateDefinitions(): array { return [ $this->jointbarTemplate(), $this->slatTemplate(), $this->screenTemplate(), $this->bendingTemplate(), ]; } // ─── 조인트바 (단일 행, 고정 기준값) ─── private function jointbarTemplate(): array { return [ 'name' => '조인트바 중간검사 성적서', 'title' => '조인트바 - 중간 검사 성적서', 'sections' => [ [ 'title' => '중간검사 기준서', 'items' => [], ], [ 'title' => '중간검사 DATA', 'items' => [ [ 'category' => '겉모양', 'item' => '가공상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '겉모양', 'item' => '조립상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '치수', 'item' => '① 높이', 'standard' => '16.5 ± 1mm', 'standard_criteria' => ['nominal' => 16.5], 'tolerance' => ['type' => 'symmetric', 'value' => 1], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => '치수', 'item' => '② 높이', 'standard' => '14.5 ± 1mm', 'standard_criteria' => ['nominal' => 14.5], 'tolerance' => ['type' => 'symmetric', 'value' => 1], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => '치수', 'item' => '③ 길이 (엔드락제외)', 'standard' => '300 ± 4mm', 'standard_criteria' => ['nominal' => 300], 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => '치수', 'item' => '④ 간격', 'standard' => '150 ± 4mm', 'standard_criteria' => ['nominal' => 150], 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], ], ], ], 'columns' => [ ['label' => '일련번호', 'column_type' => 'text', 'width' => '80px', 'sort_order' => 1], ['label' => '가공상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 2], ['label' => '조립상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 3], [ 'label' => '① 높이', 'column_type' => 'complex', 'group_name' => '① 높이', 'sub_labels' => ['기준(16.5±1)', '측정값'], 'width' => '160px', 'sort_order' => 4, ], [ 'label' => '② 높이', 'column_type' => 'complex', 'group_name' => '② 높이', 'sub_labels' => ['기준(14.5±1)', '측정값'], 'width' => '160px', 'sort_order' => 5, ], [ 'label' => '③ 길이', 'column_type' => 'complex', 'group_name' => '③ 길이', 'sub_labels' => ['기준(300±4)', '측정값'], 'width' => '160px', 'sort_order' => 6, ], [ 'label' => '④ 간격', 'column_type' => 'complex', 'group_name' => '④ 간격', 'sub_labels' => ['기준(150±4)', '측정값'], 'width' => '160px', 'sort_order' => 7, ], ['label' => '판정 (적/부)', 'column_type' => 'select', 'width' => '80px', 'sort_order' => 8], ], ]; } // ─── 슬랫 (고정 기준값 2개 + 도면치수 1개) ─── private function slatTemplate(): array { return [ 'name' => '슬랫 중간검사 성적서', 'title' => '슬랫 - 중간 검사 성적서', 'sections' => [ [ 'title' => '중간검사 기준서', 'items' => [], ], [ 'title' => '중간검사 DATA', 'items' => [ [ 'category' => '겉모양', 'item' => '가공상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '겉모양', 'item' => '조립상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '치수', 'item' => '① 높이', 'standard' => '16.5 ± 1mm', 'standard_criteria' => ['nominal' => 16.5], 'tolerance' => ['type' => 'symmetric', 'value' => 1], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => '치수', 'item' => '② 높이', 'standard' => '14.5 ± 1mm', 'standard_criteria' => ['nominal' => 14.5], 'tolerance' => ['type' => 'symmetric', 'value' => 1], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => '치수', 'item' => '③ 길이 (엔드락제외)', 'standard' => '도면치수 ± 4mm', 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['reference_attribute' => 'length'], ], ], ], ], 'columns' => [ ['label' => '일련번호', 'column_type' => 'text', 'width' => '80px', 'sort_order' => 1], ['label' => '가공상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 2], ['label' => '조립상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 3], [ 'label' => '① 높이', 'column_type' => 'complex', 'group_name' => '① 높이', 'sub_labels' => ['기준(16.5±1)', '측정값'], 'width' => '160px', 'sort_order' => 4, ], [ 'label' => '② 높이', 'column_type' => 'complex', 'group_name' => '② 높이', 'sub_labels' => ['기준(14.5±1)', '측정값'], 'width' => '160px', 'sort_order' => 5, ], [ 'label' => '③ 길이', 'column_type' => 'complex', 'group_name' => '③ 길이', 'sub_labels' => ['도면치수', '측정값'], 'width' => '160px', 'sort_order' => 6, ], ['label' => '판정 (적/부)', 'column_type' => 'select', 'width' => '80px', 'sort_order' => 7], ], ]; } // ─── 스크린 (겉모양 3개 + 치수 3개) ─── private function screenTemplate(): array { return [ 'name' => '스크린 중간검사 성적서', 'title' => '스크린 - 중간 검사 성적서', 'sections' => [ [ 'title' => '중간검사 기준서', 'items' => [], ], [ 'title' => '중간검사 DATA', 'items' => [ [ 'category' => '겉모양', 'item' => '가공상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '겉모양', 'item' => '재봉상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '겉모양', 'item' => '조립상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '치수', 'item' => '① 길이', 'standard' => '도면치수 ± 4mm', 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['reference_attribute' => 'length'], ], [ 'category' => '치수', 'item' => '② 높이', 'standard' => '도면치수 ± 40mm', 'tolerance' => ['type' => 'symmetric', 'value' => 40], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['reference_attribute' => 'height'], ], [ 'category' => '치수', 'item' => '③ 간격', 'standard' => '400 이하', 'standard_criteria' => ['max' => 400], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], ], ], ], 'columns' => [ ['label' => '일련번호', 'column_type' => 'text', 'width' => '80px', 'sort_order' => 1], ['label' => '가공상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 2], ['label' => '재봉상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 3], ['label' => '조립상태', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 4], [ 'label' => '① 길이', 'column_type' => 'complex', 'group_name' => '① 길이', 'sub_labels' => ['도면치수', '측정값'], 'width' => '160px', 'sort_order' => 5, ], [ 'label' => '② 높이', 'column_type' => 'complex', 'group_name' => '② 높이', 'sub_labels' => ['도면치수', '측정값'], 'width' => '160px', 'sort_order' => 6, ], [ 'label' => '③ 간격', 'column_type' => 'complex', 'group_name' => '③ 간격', 'sub_labels' => ['기준(400이하)', 'OK/NG'], 'width' => '160px', 'sort_order' => 7, ], ['label' => '판정 (적/부)', 'column_type' => 'select', 'width' => '80px', 'sort_order' => 8], ], ]; } // ─── 절곡품 (가장 복잡: 구성품별 다른 검사항목) ─── private function bendingTemplate(): array { return [ 'name' => '절곡품 중간검사 성적서', 'title' => '절곡품 - 중간 검사 성적서', 'sections' => [ [ 'title' => '중간검사 기준서', 'items' => [], ], [ 'title' => '중간검사 DATA', 'items' => [ // 가이드레일 [ 'category' => '가이드레일/겉모양', 'item' => '절곡상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '가이드레일/치수', 'item' => '길이', 'standard' => '도면치수 ± 4mm', 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['reference_attribute' => 'length'], ], [ 'category' => '가이드레일/치수', 'item' => '너비', 'standard' => '도면치수 ± 4mm', 'tolerance' => ['type' => 'symmetric', 'value' => 4], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['reference_attribute' => 'width'], ], [ 'category' => '가이드레일/치수', 'item' => '간격 (POINT별)', 'standard' => '도면치수 ± 2mm', 'tolerance' => ['type' => 'symmetric', 'value' => 2], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['note' => '벽면형 4P, 측면형 6P'], ], // 하단마감재 [ 'category' => '하단마감재/겉모양', 'item' => '절곡상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '하단마감재/치수', 'item' => '너비', 'standard' => '60mm 기준', 'standard_criteria' => ['nominal' => 60], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], // 케이스(셔터박스) [ 'category' => '케이스/겉모양', 'item' => '절곡상태', 'standard' => '사용상 해로운 결함이 없을 것', 'method' => 'visual', 'measurement_type' => 'checkbox', 'frequency' => '전수', 'frequency_n' => null, 'frequency_c' => null, ], [ 'category' => '케이스/치수', 'item' => '높이/하단/너비차/위치', 'standard' => '도면치수 기준', 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, 'field_values' => ['note' => '양면/밑면/후면'], ], // 하단 L-BAR / 연기차단재 [ 'category' => 'L-BAR·연기차단재/치수', 'item' => 'L-BAR 너비', 'standard' => '17mm 기준', 'standard_criteria' => ['nominal' => 17], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => 'L-BAR·연기차단재/치수', 'item' => '연기차단재 (가이드레일용)', 'standard' => '너비 50mm, 간격 12mm', 'standard_criteria' => ['너비' => 50, '간격' => 12], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], [ 'category' => 'L-BAR·연기차단재/치수', 'item' => '연기차단재 (케이스용)', 'standard' => '너비 80mm, 간격 12mm', 'standard_criteria' => ['너비' => 80, '간격' => 12], 'method' => 'check', 'measurement_type' => 'numeric', 'frequency' => '', 'frequency_n' => 1, 'frequency_c' => 0, ], ], ], ], 'columns' => [ ['label' => '분류/제품명', 'column_type' => 'text', 'width' => '120px', 'sort_order' => 1], ['label' => '타입', 'column_type' => 'text', 'width' => '100px', 'sort_order' => 2], ['label' => '겉모양(절곡상태)', 'column_type' => 'check', 'width' => '80px', 'sort_order' => 3], [ 'label' => '길이', 'column_type' => 'complex', 'group_name' => '길이', 'sub_labels' => ['도면치수', '측정값'], 'width' => '160px', 'sort_order' => 4, ], [ 'label' => '너비', 'column_type' => 'complex', 'group_name' => '너비', 'sub_labels' => ['도면치수', '측정값'], 'width' => '160px', 'sort_order' => 5, ], [ 'label' => '간격', 'column_type' => 'complex', 'group_name' => '간격', 'sub_labels' => ['POINT', '도면치수', '측정값'], 'width' => '240px', 'sort_order' => 6, ], ['label' => '판정 (적/부)', 'column_type' => 'select', 'width' => '80px', 'sort_order' => 7], ], ]; } /** * 결재라인: 작성(판매) / 검토(생산) / 승인(품질) - 5130 중간검사 공통 */ private function createApprovalLines(int $templateId): void { $lines = [ ['name' => '작성', 'dept' => '판매', 'role' => '담당자', 'sort_order' => 1], ['name' => '검토', 'dept' => '생산', 'role' => '담당자', 'sort_order' => 2], ['name' => '승인', 'dept' => '품질', 'role' => 'QC', 'sort_order' => 3], ]; foreach ($lines as $line) { DocumentTemplateApprovalLine::create(array_merge( ['template_id' => $templateId], $line, )); } } /** * 기본정보 필드 (중간검사 공통) */ private function createBasicFields(int $templateId): void { $fields = [ ['label' => '품명', 'field_type' => 'text', 'sort_order' => 1], ['label' => '규격', 'field_type' => 'text', 'sort_order' => 2], ['label' => '수주 LOT NO', 'field_type' => 'text', 'sort_order' => 3], ['label' => '로트크기', 'field_type' => 'text', 'sort_order' => 4], ['label' => '발주처', 'field_type' => 'text', 'sort_order' => 5], ['label' => '현장명', 'field_type' => 'text', 'sort_order' => 6], ['label' => '검사일자', 'field_type' => 'date', 'sort_order' => 7], ['label' => '검사자', 'field_type' => 'text', 'sort_order' => 8], ]; foreach ($fields as $field) { DocumentTemplateBasicField::create(array_merge( ['template_id' => $templateId], $field, )); } } /** * 검사 섹션 + 항목 */ private function createSection(int $templateId, string $title, array $items, int $sortOrder): void { $section = DocumentTemplateSection::create([ 'template_id' => $templateId, 'title' => $title, 'sort_order' => $sortOrder, ]); foreach ($items as $i => $item) { DocumentTemplateSectionItem::create(array_merge( ['section_id' => $section->id, 'sort_order' => $i + 1], $item, )); } } /** * 검사 기준서 동적 필드 정의 (중간검사 공통) * * 수입검사와 동일한 구조: category, item, standard(text_with_criteria), * tolerance(json_tolerance), method(select_api→measurement_type 자동매핑), * measurement_type(select), frequency(composite_frequency) */ private function createSectionFields(int $templateId): void { $fields = [ [ '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_with_criteria', 'width' => '220px', 'is_required' => false, 'sort_order' => 2, ], [ 'field_key' => 'tolerance', 'label' => '공차/범위', 'field_type' => 'json_tolerance', 'width' => '85px', 'is_required' => false, 'sort_order' => 3, ], [ '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', 'substitute_cert' => 'substitute', 'certified_agency' => 'single_value', 'other' => 'text', ], ], ], 'sort_order' => 4, ], [ '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' => '자유입력'], ], ], 'sort_order' => 5, ], [ 'field_key' => 'frequency', 'label' => '검사주기', 'field_type' => 'composite_frequency', 'width' => '120px', 'is_required' => false, 'sort_order' => 6, ], ]; foreach ($fields as $field) { DocumentTemplateSectionField::create(array_merge( ['template_id' => $templateId], $field, )); } } /** * 데이터 테이블 컬럼 (양식별 다름) */ private function createColumns(int $templateId, array $columns): void { foreach ($columns as $col) { DocumentTemplateColumn::create(array_merge( ['template_id' => $templateId], $col, )); } } private function cleanupExisting(string $name): void { $existing = DocumentTemplate::where('tenant_id', $this->tenantId) ->where('name', $name) ->first(); if (! $existing) { return; } DocumentTemplateColumn::where('template_id', $existing->id)->delete(); DocumentTemplateSectionField::where('template_id', $existing->id)->delete(); $sections = DocumentTemplateSection::where('template_id', $existing->id)->get(); foreach ($sections as $section) { DocumentTemplateSectionItem::where('section_id', $section->id)->delete(); } DocumentTemplateSection::where('template_id', $existing->id)->delete(); DocumentTemplateBasicField::where('template_id', $existing->id)->delete(); DocumentTemplateApprovalLine::where('template_id', $existing->id)->delete(); $existing->forceDelete(); } }