# MNG 문서양식관리 (Document Template Management)

> **작성일**: 2026-03-06
> **상태**: 운영 중
> **라우트**: `/document-templates`
> **관련**: [README.md](README.md) | [MNG 문서관리](mng-document-system.md)

---

## 1. 개요

문서관리 시스템에서 사용하는 **서식(Template)**을 생성, 편집, 복제, 관리하는 기능. 검사 성적서, 작업지시서 등 다양한 문서 양식을 정의하며, 2가지 빌더 타입을 지원한다.

| 빌더 | builder_type | UI 명칭 | 설명 |
|------|-------------|---------|------|
| **Legacy Builder** | `legacy` 또는 null | 새 양식 | 탭 기반 폼 UI (순수 JavaScript) |
| **Block Builder** | `block` | 양식 디자이너 | WYSIWYG 캔버스 편집기 (Alpine.js + SortableJS) |

> **명칭 변경 이력**: Block Builder의 UI 표시 명칭이 '블록 빌더' → '양식 디자이너'로 변경됨 (2026-02-28)

**핵심 기능:**
- 결재선, 기본필드, 검사 기준서, 테이블 컬럼 정의
- EAV 데이터 구조의 서식 스키마 관리
- 양식 복제 (연결품목 제외)
- 프리셋 자동 제안 (카테고리별)
- 소프트 삭제 + 휴지통 관리 (슈퍼어드민)

---

## 2. 라우트

### 2.1 웹 라우트 (페이지)

```
GET  /document-templates              → index       (목록)
GET  /document-templates/create       → create      (Legacy 신규 생성)
GET  /document-templates/block-create → blockCreate (양식 디자이너 신규 생성)
GET  /document-templates/{id}/edit    → edit        (Legacy 편집)
GET  /document-templates/{id}/block-edit → blockEdit (양식 디자이너 편집)
```

### 2.2 API 라우트 (CRUD + 기능)

```
Prefix: /api/admin/document-templates (HQ 관리자 전용)

GET    /                              → index         (HTMX 테이블)
POST   /                              → store         (생성)
GET    /{id}                          → show          (상세 조회)
PUT    /{id}                          → update        (수정)
DELETE /{id}                          → destroy       (소프트 삭제)
DELETE /{id}/force                    → forceDestroy  (영구삭제, 슈퍼어드민)
POST   /{id}/restore                  → restore       (복원, 슈퍼어드민)
POST   /{id}/toggle-active            → toggleActive  (활성 토글)
POST   /{id}/duplicate                → duplicate     (복제)
POST   /upload-image                  → uploadImage   (이미지 업로드)
GET    /admin/common-codes/{group}    → getCommonCodes (공통코드 조회)
```

---

## 3. 모델 구조

### 3.1 모델 관계도

```
DocumentTemplate (서식 마스터)
├── 1:N  DocumentTemplateApprovalLine   (결재선)
├── 1:N  DocumentTemplateBasicField     (기본필드)
├── 1:N  DocumentTemplateSection        (섹션/기준서)
│         └── 1:N  DocumentTemplateSectionItem  (섹션 항목)
├── 1:N  DocumentTemplateSectionField   (섹션 필드)
├── 1:N  DocumentTemplateColumn         (테이블 컬럼)
└── 1:N  DocumentTemplateLink           (연결 설정)
           └── 1:N  DocumentTemplateLinkValue   (연결 값)
```

### 3.2 DocumentTemplate 핵심 필드

```php
// 기본 정보
builder_type    // 'legacy' | 'block'
name            // 양식명
category        // 분류명
title           // 문서 제목

// 회사 정보
company_name    // 회사명
company_address // 회사 주소
company_contact // 회사 연락처

// 하단 설정
footer_remark_label       // 비고 라벨
footer_judgement_label     // 판정 라벨
footer_judgement_options   // array - 판정 선택지

// Block Builder 전용
schema          // array - 블록 스키마 (JSON)
page_config     // array - 페이지 설정 (A4/A3, 여백 등)

// 연결 (레거시)
linked_item_ids     // array - 연결 품목 ID 목록
linked_process_id   // int - 연결 공정 ID

// 상태
is_active       // boolean - 활성 여부
deleted_at      // timestamp - 소프트 삭제
deleted_by      // int - 삭제자
```

**Helper 메서드:**

```php
isBlockBuilder(): bool   // builder_type === 'block'
isLegacyBuilder(): bool  // builder_type !== 'block'
```

### 3.3 DocumentTemplateApprovalLine (결재선)

| 필드 | 타입 | 설명 |
|------|------|------|
| `template_id` | FK | 서식 ID |
| `name` | string | 결재자 이름/직책 |
| `department` | string | 부서 |
| `role` | string | 역할 (작성/검토/승인) |
| `sort_order` | int | 순서 |

### 3.4 DocumentTemplateBasicField (기본필드)

| 필드 | 타입 | 설명 |
|------|------|------|
| `template_id` | FK | 서식 ID |
| `field_key` | string | 필드 키 (bf_ 접두사) |
| `label` | string | 라벨 |
| `field_type` | string | text, date, select 등 |
| `default_value` | string | 기본값 |
| `is_required` | boolean | 필수 여부 |
| `sort_order` | int | 순서 |
| `options` | array | 선택지 (select 타입) |

### 3.5 DocumentTemplateSection (섹션/검사 기준서)

| 필드 | 타입 | 설명 |
|------|------|------|
| `template_id` | FK | 서식 ID |
| `title` | string | 섹션 제목 |
| `image_path` | string | 섹션 이미지 경로 |
| `sort_order` | int | 순서 |

**하위 관계:**

```
Section 1:N SectionItem
    ├── category        // 카테고리 (그룹핑)
    ├── name            // 항목명
    ├── standard        // 기준
    ├── tolerance_type  // 공차 유형 (symmetric/asymmetric/range/limit)
    ├── tolerance_plus  // +공차
    ├── tolerance_minus // -공차
    ├── reference_value // 기준값
    ├── method          // 검사방법
    ├── measurement_type // 측정유형
    └── frequency       // 검사주기
```

### 3.6 DocumentTemplateColumn (테이블 컬럼)

| 필드 | 타입 | 설명 |
|------|------|------|
| `template_id` | FK | 서식 ID |
| `label` | string | 컬럼 라벨 |
| `group_name` | string | 그룹명 (다단계 "/" 구분) |
| `width` | int | 컬럼 너비 |
| `column_type` | string | text, check, complex, select, measurement |
| `sub_labels` | array | complex 타입 하위 라벨 |
| `sort_order` | int | 순서 |

### 3.7 DocumentTemplateLink (연결 설정)

| 필드 | 타입 | 설명 |
|------|------|------|
| `template_id` | FK | 서식 ID |
| `link_key` | string | 연결 키 |
| `label` | string | 라벨 |
| `link_type` | string | `single` / `multiple` |
| `source_table` | string | `items` / `processes` / `users` |
| `search_params` | array | 검색 파라미터 |
| `display_fields` | array | 표시 필드 |
| `is_required` | boolean | 필수 여부 |
| `sort_order` | int | 순서 |

**하위 관계:**

```
Link 1:N LinkValue
    ├── link_id        // FK → Link
    ├── linkable_id    // 연결 엔티티 ID
    └── (source_table에 따라 items/processes/users 참조)
```

**레거시 호환 처리:**

```php
// 신규 links가 있으면 사용
if ($template->links->isNotEmpty()) {
    // template_links + link_values 사용
}

// 레거시만 있으면 가상 엔트리 생성
if (!empty($template->linked_item_ids)) {
    return [['link_key' => 'items', 'values' => [...]]]
}
```

---

## 4. 컨트롤러 상세

### 4.1 DocumentTemplateController (웹)

| 메서드 | 동작 |
|--------|------|
| `index()` | HTMX 요청 → HX-Redirect 반환 (전체 페이지 로드 강제) |
| `create()` | Legacy 신규 생성 폼 렌더링 |
| `edit($id)` | Legacy 편집. 양식 디자이너 타입이면 `blockEdit`으로 자동 리다이렉트 |
| `blockCreate()` | 양식 디자이너 신규 생성 (빈 캔버스) |
| `blockEdit($id)` | 양식 디자이너 편집 (스키마 로드) |

**공통 데이터 준비:**

```php
// 현재 테넌트 조회
$tenantId = getCurrentTenant();  // 세션의 selected_tenant_id

// 카테고리 목록 = common_codes + 기존 템플릿 카테고리
$categories = getCategories();

// 기본필드 키 옵션
$basicFieldKeys = getBasicFieldKeys();  // common_codes 'doc_template_basic_field'
```

### 4.2 DocumentTemplateApiController (API)

#### `index()` — HTMX 테이블 조회

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 양식명/분류 검색 |
| `category` | string | 분류 필터 |
| `is_active` | string | `1` / `0` / `TRASHED` (휴지통) |

```php
// 휴지통 모드 (슈퍼어드민 전용)
if ($isActive === 'TRASHED') {
    $query->onlyTrashed();
}
```

#### `store()` / `update()` — 생성/수정

```
요청 데이터
    ↓
검증 (직접 validate, FormRequest 미사용)
    ↓
연결품목 중복 검증 (checkLinkedItemDuplicates)
    ↓
DB::transaction 시작
    ↓
Template 생성/수정
    ↓
saveRelations() — 관계 데이터 upsert
    ↓
DB::transaction 완료
    ↓
JSON 응답
```

#### `duplicate()` — 양식 복제

```php
$source = DocumentTemplate::with([...all relationships...]);

$newTemplate = DocumentTemplate::create([
    ...원본 데이터,
    'name' => request('name', '원본 (복사)'),
    'is_active' => false,           // 비활성으로 생성
    'linked_item_ids' => null,       // 연결품목 제외
    'linked_process_id' => null,     // 연결공정 제외
]);

// 각 관계 데이터 복사 (approvalLines, basicFields, sections, columns...)
// linkValues는 복사 안 함 (동일 분류 내 중복 방지)
```

#### `forceDestroy()` — 영구삭제

```php
// 사전 검사: 참조하는 문서 존재 여부
$documentCount = Document::withTrashed()
    ->where('template_id', $id)
    ->count();

if ($documentCount > 0) {
    return 422; // "이 양식을 사용한 문서 {count}건이 있어 삭제 불가"
}
```

#### `uploadImage()` — 이미지 업로드

```
요청 (multipart)
    ↓
ApiTokenService::exchangeToken($userId, $tenantId)
    ↓
API /files/upload 호출 (Bearer 토큰)
    ↓
응답: file_path (1/temp/2026/02/xxx.jpg)
    ↓
최종 URL: http://api.sam.kr/storage/tenants/{file_path}
```

---

## 5. 저장 메커니즘 (saveRelations)

### 5.1 upsert 전략

| 관계 | 방식 | 이유 |
|------|------|------|
| approvalLines | 전체 삭제 → 재생성 | ID 참조 없음 |
| basicFields | 전체 삭제 → 재생성 | ID 참조 없음 |
| **sections** | **ID 보존 upsert** | document_data가 section_id 참조 |
| **sectionItems** | **ID 보존 upsert** | section 하위 항목 |
| **columns** | **ID 보존 upsert** | document_data가 column_id 참조 |
| sectionFields | 전체 삭제 → 재생성 | ID 참조 없음 |
| links + linkValues | 전체 삭제 → 재생성 | ID 참조 없음 |

### 5.2 ID 보존 upsert 로직

```php
// 1. 요청 ID 수집
$incomingIds = collect($data['sections'])->pluck('id')->filter();

// 2. 요청에 없는 항목 삭제
$template->sections()
    ->whereNotIn('id', $incomingIds)
    ->each(function($s) {
        $s->items()->delete();
        $s->delete();
    });

// 3. 각 항목 upsert
foreach ($data['sections'] as $section) {
    if (!empty($section['id']) && $existing = $template->sections()->find($section['id'])) {
        $existing->update($sectionData);  // 기존: update
    } else {
        DocumentTemplateSection::create([...]); // 신규: create
    }
}
```

> **ID 보존이 필수인 이유**: `document_data` 테이블이 `section_id`, `column_id`를 FK로 참조한다. 양식 수정 시 ID가 변경되면 기존 문서 데이터와의 매핑이 깨진다.

---

## 6. 화면 구성

### 6.1 목록 화면 (`index.blade.php`)

```
┌─────────────────────────────────────────────────┐
│ 문서양식관리                                      │
│ [+ 새 양식]  [+ 양식 디자이너]                      │
├─────────────────────────────────────────────────┤
│ 필터: [검색어] [분류 ▼] [활성/비활성/휴지통 ▼]      │
├─────────────────────────────────────────────────┤
│ # │ 양식명 │ 분류 │ 활성 │ 수정일 │ 액션          │
│ 1 │ FQC... │ 검사 │ ✅   │ 03-06 │ 편집 복제 삭제 │
│ 2 │ 수입... │ 검사 │ ✅   │ 03-05 │ 편집 복제 삭제 │
│ ...│        │      │      │       │               │
└─────────────────────────────────────────────────┘
```

**HTMX 테이블 로드:**

```html
<div id="template-table"
     hx-get="/api/admin/document-templates"
     hx-trigger="load, filterSubmit from:body"
     hx-swap="innerHTML">
</div>
```

**액션 버튼:**
- **편집**: 새 양식 → `/document-templates/{id}/edit`, 양식 디자이너 → `/document-templates/{id}/block-edit`
- **복제**: `duplicateTemplate(id)` — 이름 입력 모달 후 POST
- **삭제**: `confirmDelete(id)` — 확인 후 DELETE
- **미리보기**: `previewTemplate(id)` — 모달 표시
- **활성 토글**: `toggleActive(id)` — POST toggle-active
- **복원/영구삭제**: 휴지통 모드에서만 표시 (슈퍼어드민)

### 6.2 Legacy Builder 편집 화면 (`edit.blade.php`)

**4개 탭 구조:**

```
┌─────────────────────────────────────────────────────┐
│ [기본정보] [기본필드] [검사 기준서] [테이블 컬럼]       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  (각 탭 콘텐츠)                                      │
│                                                     │
├─────────────────────────────────────────────────────┤
│                        [미리보기]  [저장]  [취소]     │
└─────────────────────────────────────────────────────┘
```

#### 탭 1: 기본정보

| 필드 | 설명 |
|------|------|
| 양식명 | 서식 이름 (필수) |
| 제목 | 문서 제목 |
| 분류 | 카테고리 (common_codes + 기존값) |
| 회사명 | 문서 헤더 회사명 |
| 회사 주소/연락처 | 문서 헤더 |
| 활성 | 체크박스 |
| 결재선 | 동적 행 추가/삭제 (이름, 부서, 역할) |

#### 탭 2: 기본필드

| 항목 | 설명 |
|------|------|
| 필드 키 | `bf_` 접두사 (common_codes에서 선택) |
| 라벨 | 표시 라벨 |
| 필드 타입 | text, date, select 등 |
| 기본값 | 문서 생성 시 자동 입력 |
| 필수 여부 | 체크박스 |

#### 탭 3: 검사 기준서

```
┌──────────────────────────────────────────────────┐
│ 섹션 1: [제목 입력]  [이미지 업로드]  [+ 항목 추가] │
│ ┌──────────────────────────────────────────────┐ │
│ │ 카테고리 │ 항목 │ 기준 │ 공차 │ 기준값 │ ...   │ │
│ │ 외관     │ 색상 │ 기준 │ ±0.5 │ 5.0  │ ...   │ │
│ │ 외관     │ 흠집 │ 무   │      │      │ ...   │ │
│ └──────────────────────────────────────────────┘ │
│                                                  │
│ 섹션 2: [제목 입력]  [이미지 업로드]  [+ 항목 추가] │
│ ...                                              │
│                                  [+ 섹션 추가]    │
└──────────────────────────────────────────────────┘
```

**공차 유형:**

| 유형 | 입력 | 표시 예 |
|------|------|--------|
| `symmetric` | ± 값 | ±0.5 |
| `asymmetric` | +값, -값 | +0.3 / -0.2 |
| `range` | 최소~최대 | 4.5 ~ 5.5 |
| `limit` | 상한 또는 하한 | ≤ 10 |

#### 탭 4: 테이블 컬럼

| 항목 | 설명 |
|------|------|
| 라벨 | 컬럼 헤더 |
| 그룹명 | 다단계 그룹 ("/" 구분) |
| 너비 | 컬럼 너비 (px 또는 %) |
| 컬럼 타입 | text, check, complex, select, measurement |
| 하위 라벨 | complex 타입 시 sub_labels |

**자동 컬럼 생성:**

```
[기준서에서 자동 생성] 버튼 클릭
    ↓
검사 기준서 섹션의 항목들을 분석
    ↓
카테고리 그룹별 컬럼 자동 생성
    ↓
measurement_type에 따라 컬럼 타입 결정
```

### 6.3 양식 디자이너 편집 화면 (`block-editor.blade.php`)

**3패널 레이아웃:**

```
┌──────────┬──────────────────────────┬───────────┐
│ 팔레트    │ 캔버스                    │ 속성 패널  │
│ (220px)  │ (flex: 1)               │ (300px)   │
│          │                          │           │
│ 기본:     │ ┌──────────────────────┐ │ 선택 블록: │
│ □ 제목    │ │ [제목 블록]           │ │           │
│ □ 문단    │ │ [문단 블록]           │ │ 제목: ... │
│ □ 테이블  │ │ [테이블 블록]         │ │ 크기: ... │
│ □ 컬럼    │ │ [입력 필드 블록]      │ │ 정렬: ... │
│ □ 구분선  │ │                      │ │           │
│ □ 여백    │ └──────────────────────┘ │           │
│          │                          │           │
│ 폼:      │                          │           │
│ □ 텍스트  │                          │           │
│ □ 숫자    │                          │           │
│ □ 날짜    │                          │           │
│ □ 선택    │                          │           │
│ □ 체크    │                          │           │
│ □ 텍스트영역│                        │           │
│ □ 서명    │                          │           │
└──────────┴──────────────────────────┴───────────┘
```

**블록 타입 (15개):**

| 분류 | 타입 | 설명 |
|------|------|------|
| 기본 | `heading` | 제목 (h1~h6) |
| 기본 | `paragraph` | 문단 텍스트 |
| 기본 | `table` | 테이블 (행/열 편집) |
| 기본 | `columns` | 다단 컬럼 레이아웃 |
| 기본 | `divider` | 구분선 |
| 기본 | `spacer` | 여백 |
| 폼 | `text_field` | 텍스트 입력 |
| 폼 | `number_field` | 숫자 입력 |
| 폼 | `date_field` | 날짜 입력 |
| 폼 | `select_field` | 선택 드롭다운 |
| 폼 | `checkbox_field` | 체크박스 |
| 폼 | `textarea_field` | 긴 텍스트 입력 |
| 폼 | `signature_field` | 서명 영역 |

**Alpine.js 상태 관리:**

```javascript
blockEditor(initialSchema, templateId) {
    blocks: [],            // 블록 배열
    selectedBlockId: null, // 현재 선택 블록
    history: [],           // Undo/Redo 스택 (최대 50)
    historyIndex: -1,
    pageConfig: {          // 페이지 설정
        size: 'A4',        // A4 / A3
        orientation: 'portrait',  // portrait / landscape
        margins: { top, right, bottom, left }
    },
    templateName: '',
    category: ''
}
```

**키보드 단축키:**

| 단축키 | 기능 |
|--------|------|
| `Ctrl+Z` / `Cmd+Z` | Undo |
| `Ctrl+Shift+Z` / `Cmd+Shift+Z` | Redo |
| `Ctrl+S` / `Cmd+S` | 저장 |

**SortableJS:**
- 캔버스 내 블록 드래그-앤-드롭 정렬
- 팔레트에서 캔버스로 블록 추가

---

## 7. 미리보기 시스템

### 7.1 Legacy Builder 미리보기

```javascript
buildDocumentPreviewHtml(data)
├── 결재란 테이블 (역할별 칸)
├── 기본필드 (2열 15:35:15:35 비율)
├── 섹션별 이미지 (title + image 또는 placeholder)
├── 검사 데이터 테이블
│   ├── 다단계 그룹 헤더 (group_name "/" 구분)
│   ├── sub_labels (complex 컬럼)
│   ├── 항목 행 (카테고리 그룹핑)
│   └── 측정치 셀 (measurement_type별 렌더)
└── 비고/종합판정 섹션
```

### 7.2 양식 디자이너 미리보기

```javascript
buildBlockPreviewHtml(data)
├── 블록 타입별 HTML 렌더링
├── 폼 필드 placeholder 표시
└── A4/A3 레이아웃 시뮬레이션
```

### 7.3 이미지 URL 처리

```javascript
_previewImageUrl(imagePath)
├── http(s):// 시작 → 그대로 사용
├── /^\d+\// 패턴 → API tenant storage URL 생성
│   → http://api.sam.kr/storage/tenants/{imagePath}
└── 기타 → MNG local storage (/storage/{imagePath})
```

---

## 8. 분류(Category) 관리

### 8.1 소스 (우선순위)

1. **common_codes** (code_group = `document_category`, is_active = true)
   - tenant_id가 있는 것 우선 (테넌트 전용)
   - tenant_id가 null인 것도 포함 (공통)
   - code 기준 중복 제거 (테넌트 우선)
2. **기존 템플릿의 category** (common_codes에 없는 값)
   - 기존 이름 그대로 추가

### 8.2 연동 공통코드 그룹

| 그룹 | 용도 |
|------|------|
| `document_category` | 문서 분류 |
| `doc_template_basic_field` | 기본필드 키 옵션 |
| `doc_inspection_method` | 검사방법 |
| `doc_measurement_type` | 측정유형 |

---

## 9. 프리셋 시스템

### 9.1 테이블

```
document_template_field_presets
├── name               // 프리셋 이름
├── category           // 대상 카테고리
├── description        // 설명
└── field_definitions  // array - 필드 정의 목록
    [{ field_key, label, field_type, options, ... }]
```

### 9.2 동작

```
분류(Category) 변경
    ↓
매칭 프리셋 검색
    ↓
기존 section_fields가 비어있으면
    ↓
"'{category}' 카테고리에 맞는 프리셋을 적용할까요?" 확인
    ↓
승인 시 field_definitions 자동 적용
```

> **주의**: 초기 로드 시에는 제안하지 않음. 분류 변경 시에만 제안.

---

## 10. 연결품목 중복 검증

### 10.1 규칙

같은 category 내 서로 다른 템플릿이 동일한 items를 연결할 수 없다.

### 10.2 검증 로직

```php
checkLinkedItemDuplicates($templateId, $category, $itemIds)

// 1. 같은 category의 다른 템플릿 조회
$otherTemplates = DocumentTemplate::where('category', $category)
    ->where('id', '!=', $templateId)
    ->get();

// 2. 각 템플릿의 연결품목 수집
foreach ($otherTemplates as $other) {
    // 레거시: linked_item_ids (JSON 배열)
    // 신규: template_links → linkValues (source_table = 'items')
    $existingItemIds = ...;
}

// 3. 교집합 검사
$duplicates = array_intersect($itemIds, $existingItemIds);
if (!empty($duplicates)) {
    return 422; // 중복 항목 목록과 함께 오류 반환
}
```

---

## 11. JavaScript 상태 관리 (Legacy Builder)

### 11.1 templateState 객체

```javascript
const templateState = {
    // 기본정보
    id, name, category, title,
    company_name, company_address, company_contact,
    footer_remark_label, footer_judgement_label,
    footer_judgement_options,
    is_active,

    // 관계 데이터
    approval_lines: [],      // 결재선
    basic_fields: [],        // 기본필드
    sections: [],            // 섹션 + items
    columns: [],             // 테이블 컬럼
    section_fields: [],      // 섹션 필드
    template_links: [],      // 연결 설정 + values
};
```

### 11.2 저장 흐름

```
사용자 입력 (Blade 폼)
    ↓
templateState 객체 갱신
    ↓
saveTemplate() 호출
    ↓
fetch POST/PUT /api/admin/document-templates
    ↓
DocumentTemplateApiController::store/update()
    ↓
검증 → 중복 검사 → DB 트랜잭션 → saveRelations()
    ↓
JSON 응답
    ↓
showToast() 메시지
    ↓
htmx.trigger('#template-table', 'filterSubmit') → 테이블 새로고침
```

---

## 12. 양식 디자이너(Block Builder) vs 새 양식(Legacy Builder) 비교

| 항목 | 양식 디자이너 | 새 양식 |
|------|:------------:|:-------------:|
| builder_type | `block` | `legacy` 또는 null |
| 편집 UI | WYSIWYG 캔버스 (Alpine.js) | 탭 폼 (순수 JavaScript) |
| 데이터 저장 | `schema` JSON 컬럼 | 관계 테이블 (7개) |
| Undo/Redo | 히스토리 스택 (최대 50) | 불가 |
| 블록 타입 | 15개 (기본 6 + 폼 7 + 기타 2) | N/A |
| 드래그-앤-드롭 | SortableJS | 불가 |
| 페이지 설정 | A4/A3, 여백, 방향 | 없음 |
| 복제 | 스키마 JSON 복사 | 각 관계 데이터 개별 복사 |
| 미리보기 함수 | `buildBlockPreviewHtml()` | `buildDocumentPreviewHtml()` |
| 적합 용도 | 자유 레이아웃 문서 | 정형화된 검사 성적서 |

---

## 13. 권한 및 보안

### 13.1 미들웨어

- **웹 라우트**: 일반 인증 (auth)
- **API 라우트**: HQ 관리자 미들웨어 (`admin` prefix)

### 13.2 슈퍼어드민 전용 기능

| 기능 | 엔드포인트 |
|------|-----------|
| 영구삭제 | `DELETE /{id}/force` |
| 복원 | `POST /{id}/restore` |
| 휴지통 조회 | `GET /?is_active=TRASHED` |

### 13.3 삭제 보호

- 소프트 삭제: `deleted_at` + `deleted_by` 기록
- 영구삭제 전 참조 문서 검사 (Document 테이블)
- 참조 문서가 있으면 영구삭제 불가 (422 응답)

---

## 14. API 프로젝트 연동

### 14.1 API 서비스

```php
// DocumentTemplateService (API)
list(array $params): LengthAwarePaginator
    // 필터: is_active, category, search

show(int $id): DocumentTemplate
    // 전체 관계 로드 (approvalLines, basicFields, sections, columns...)
```

### 14.2 API 엔드포인트

```
GET  /v1/document-templates      → index (목록)
GET  /v1/document-templates/{id} → show  (상세)
```

> API는 **읽기 전용**. 서식 생성/수정은 MNG에서만 수행.

---

## 15. 주요 파일 경로

| 기능 | 경로 |
|------|------|
| 웹 컨트롤러 | `mng/app/Http/Controllers/DocumentTemplateController.php` |
| API 컨트롤러 | `mng/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php` |
| 모델 (8개) | `mng/app/Models/DocumentTemplate*.php` |
| 뷰 - 목록 | `mng/resources/views/document-templates/index.blade.php` |
| 뷰 - Legacy 편집 | `mng/resources/views/document-templates/edit.blade.php` |
| 뷰 - 양식 디자이너 | `mng/resources/views/document-templates/block-editor.blade.php` |
| 뷰 - 테이블 | `mng/resources/views/document-templates/partials/table.blade.php` |
| 뷰 - 미리보기 | `mng/resources/views/document-templates/partials/preview-modal.blade.php` |
| API 서비스 | `api/app/Services/DocumentTemplateService.php` |
| API 모델 | `api/app/Models/Documents/DocumentTemplate*.php` |

---

## 재공품 문서양식 구성 (2026-03-21)

### 개요

재공품 공정(P-004~P-007)별로 **작업일지**와 **중간검사 성적서** 양식을 분리 구성했다. 원본(#67, #68)을 복사하여 공정별 전용 양식을 생성하고, 검사 기준서 항목을 해당 공정 부품만 남도록 분류했다.

### 양식 목록

| 공정 | 작업일지 | 중간검사 성적서 |
|------|---------|---------------|
| P-004 가이드레일 | #69 재공품 가이드레일 작업일지 | #70 재공품 가이드레일 중간검사 성적서 |
| P-005 케이스 | #71 재공품 케이스 작업일지 | #72 재공품 케이스 중간검사 성적서 |
| P-006 하단마감재 | #73 재공품 하단마감재 작업일지 | #74 재공품 하단마감재 중간검사 성적서 |
| P-007 기타 | #75 재공품 기타 작업일지 | #76 재공품 기타 중간검사 성적서 |

> 원본 #67(중간검사), #68(작업일지)는 soft delete 상태.

### 검사 기준서 항목 분류

원본 #67의 "중간검사 DATA" 섹션 10개 항목을 `category` 기준으로 공정별 분배:

| 양식 | 검사항목 |
|------|---------|
| #70 가이드레일 | 가이드레일/겉모양(절곡상태), 가이드레일/치수(길이/너비/간격) — 4건 |
| #72 케이스 | 케이스/겉모양(절곡상태), 케이스/치수(높이/하단/너비차/위치) — 2건 |
| #74 하단마감재 | 하단마감재/겉모양(절곡상태), 하단마감재/치수(너비) — 2건 |
| #76 기타 | 연기차단재/치수(가이드레일용), 연기차단재/치수(케이스용) — 2건 |

### 기본필드 연동키 매핑

문서 생성 시 자동으로 값을 채우는 연동키 설정 (8개 양식 60건 일괄 적용):

| 필드 라벨 | 연동키 (field_key) | 자동 입력 데이터 |
|----------|-------------------|-----------------|
| 부품명 | `product_name` | 품목명 |
| 부품코드 | `product_code` | 품목코드 |
| 재질/규격 | `specification` | 규격 정보 |
| 수주/제품 LOT NO | `lot_no` | LOT 번호 |
| 로트크기 | `lot_size` | 로트 크기 |
| 발주처/수주처 | `client` | 거래처명 |
| 현장명 | `site_name` | 현장명 |
| 검사일자 | `inspection_date` | 검사 날짜 |
| 검사자 | `inspector` | 검사자명 |

**미매핑 필드** (연동키 미존재, 수동 입력):
- 수주일, 작업일자, 생산담당자, 출고예정일

### UI 개선 사항 (2026-03-21)

- **복사 버튼**: 문서양식 편집 화면에 추가. 전체 탭 내용(기본정보, 결재라인, 검사 기준서, 테이블 컬럼) 복제
- **상단 sticky 고정**: 양식명/문서제목/분류/회사명 + 탭 네비게이션이 스크롤 시 상단 고정. 기본정보 탭에서는 중복 방지를 위해 숨김
- **Legacy 양식 fallback**: `section_fields`가 없는 legacy 양식에서 고정 컬럼(분류/검사항목/검사기준/검사방식/측정방법/측정수)으로 자동 렌더링
- **sub_labels 안전 처리**: `Array.isArray()` 체크 추가 (문자열로 저장된 경우 TypeError 방지)

---

## 관련 문서

- [README.md](README.md) — 문서관리 시스템 개요 (API 중심)
- [MNG 문서관리](mng-document-system.md) — 문서 생성/편집/결재 (서식을 사용하는 측)
- [DB 스키마 — 문서](../../system/database/documents.md)
- [절곡 바라시 기초자료](../bending/README.md) — 재공품 공정 구조, 품목코드 체계

---

**최종 업데이트**: 2026-03-21
