Files
sam-docs/sam/docs/features/documents/mng-document-template.md

27 KiB

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

작성일: 2026-03-06 상태: 운영 중 라우트: /document-templates 관련: README.md | MNG 문서관리


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 핵심 필드

// 기본 정보
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 메서드:

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 순서
필드 타입 설명
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 참조)

레거시 호환 처리:

// 신규 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) 양식 디자이너 편집 (스키마 로드)

공통 데이터 준비:

// 현재 테넌트 조회
$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 (휴지통)
// 휴지통 모드 (슈퍼어드민 전용)
if ($isActive === 'TRASHED') {
    $query->onlyTrashed();
}

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

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

duplicate() — 양식 복제

$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() — 영구삭제

// 사전 검사: 참조하는 문서 존재 여부
$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 로직

// 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 테이블 로드:

<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 상태 관리:

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 미리보기

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

7.2 양식 디자이너 미리보기

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

7.3 이미지 URL 처리

_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 검증 로직

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 객체

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 서비스

// 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-06