diff --git a/sam/docs/features/documents/README.md b/sam/docs/features/documents/README.md index 6864577..ab1f4d5 100644 --- a/sam/docs/features/documents/README.md +++ b/sam/docs/features/documents/README.md @@ -112,6 +112,7 @@ DRAFT → PENDING → APPROVED ## 관련 문서 - [MNG 문서관리 시스템 상세](mng-document-system.md) — MNG 화면 구성, 탭별 기능, 서식 빌더, EAV 저장 패턴 상세 +- [MNG 문서양식관리](mng-document-template.md) — 서식 생성/편집, Legacy/Block Builder, 프리셋, 연결품목 관리 - [DB 스키마 — 문서/전자서명](../../system/database/documents.md) - [게시판 시스템](../boards/README.md) — 유사한 EAV 패턴 적용 - Swagger: `/api-docs` → Documents 섹션 diff --git a/sam/docs/features/documents/mng-document-template.md b/sam/docs/features/documents/mng-document-template.md new file mode 100644 index 0000000..5ced3e0 --- /dev/null +++ b/sam/docs/features/documents/mng-document-template.md @@ -0,0 +1,824 @@ +# MNG 문서양식관리 (Document Template Management) + +> **작성일**: 2026-03-06 +> **상태**: 운영 중 +> **라우트**: `/document-templates` +> **관련**: [README.md](README.md) | [MNG 문서관리](mng-document-system.md) + +--- + +## 1. 개요 + +문서관리 시스템에서 사용하는 **서식(Template)**을 생성, 편집, 복제, 관리하는 기능. 검사 성적서, 작업지시서 등 다양한 문서 양식을 정의하며, 2가지 빌더 타입을 지원한다. + +| 빌더 | builder_type | 설명 | +|------|-------------|------| +| **Legacy Builder** | `legacy` 또는 null | 탭 기반 폼 UI (순수 JavaScript) | +| **Block Builder** | `block` | WYSIWYG 캔버스 편집기 (Alpine.js + SortableJS) | + +**핵심 기능:** +- 결재선, 기본필드, 검사 기준서, 테이블 컬럼 정의 +- EAV 데이터 구조의 서식 스키마 관리 +- 양식 복제 (연결품목 제외) +- 프리셋 자동 제안 (카테고리별) +- 소프트 삭제 + 휴지통 관리 (슈퍼어드민) + +--- + +## 2. 라우트 + +### 2.1 웹 라우트 (페이지) + +``` +GET /document-templates → index (목록) +GET /document-templates/create → create (Legacy 신규 생성) +GET /document-templates/block-create → blockCreate (Block Builder 신규 생성) +GET /document-templates/{id}/edit → edit (Legacy 편집) +GET /document-templates/{id}/block-edit → blockEdit (Block Builder 편집) +``` + +### 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 편집. Block Builder 타입이면 `blockEdit`으로 자동 리다이렉트 | +| `blockCreate()` | Block Builder 신규 생성 (빈 캔버스) | +| `blockEdit($id)` | Block Builder 편집 (스키마 로드) | + +**공통 데이터 준비:** + +```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`) + +``` +┌─────────────────────────────────────────────────┐ +│ 문서양식관리 │ +│ [+ 새 양식 (Legacy)] [+ 양식 디자이너 (Block)] │ +├─────────────────────────────────────────────────┤ +│ 필터: [검색어] [분류 ▼] [활성/비활성/휴지통 ▼] │ +├─────────────────────────────────────────────────┤ +│ # │ 양식명 │ 분류 │ 활성 │ 수정일 │ 액션 │ +│ 1 │ FQC... │ 검사 │ ✅ │ 03-06 │ 편집 복제 삭제 │ +│ 2 │ 수입... │ 검사 │ ✅ │ 03-05 │ 편집 복제 삭제 │ +│ ...│ │ │ │ │ │ +└─────────────────────────────────────────────────┘ +``` + +**HTMX 테이블 로드:** + +```html +
+
+``` + +**액션 버튼:** +- **편집**: Legacy → `/document-templates/{id}/edit`, Block → `/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 Builder 편집 화면 (`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 Block Builder 미리보기 + +```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 비교 + +| 항목 | Block Builder | 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` | +| 뷰 - Block Builder | `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` | + +--- + +## 관련 문서 + +- [README.md](README.md) — 문서관리 시스템 개요 (API 중심) +- [MNG 문서관리](mng-document-system.md) — 문서 생성/편집/결재 (서식을 사용하는 측) +- [DB 스키마 — 문서](../../system/database/documents.md) + +--- + +**최종 업데이트**: 2026-03-06