Files
sam-docs/sam/docs/plans/block-builder-evolution-plan.md
김보곤 51446080db docs: [plans] 양식 디자이너 고도화 계획 수립 (6 Phase)
- Phase 2: 블록 런타임 렌더러 + EAV 데이터 바인딩
- Phase 3: 결재선 블록 + 워크플로우 연동
- Phase 4: 동적 테이블 + 변수/매크로 시스템
- Phase 5: 수식 엔진 + 조건부 표시 + 이미지 블록
- Phase 6: 인쇄/PDF + Legacy Builder 대체
- INDEX.md에 계획 문서 등록
2026-03-06 09:29:06 +09:00

21 KiB

양식 디자이너(Block Builder) 고도화 계획

작성일: 2026-03-06 상태: 계획 수립 담당: Claude Code + 개발팀 관련: 문서양식관리 | 문서관리


1. 현재 상태 진단

1.1 구현 완료 (Phase 1 — 2026-02-28)

  • 13개 블록 타입 (기본 6 + 폼 7)
  • 3패널 UI (팔레트 / 캔버스 / 속성)
  • SortableJS 드래그-앤-드롭 정렬
  • Undo/Redo (최대 50단계)
  • JSON 스키마 저장 (document_templates.schema)
  • 페이지 설정 (A4/A3/B5, 세로/가로, 여백)

1.2 핵심 미구현 사항

기능 상태 영향도
문서 생성 시 블록 렌더링 미구현 블록 서식으로 문서 작성 불가
결재선 블록 미구현 결재 워크플로우 연동 불가
데이터 바인딩 (EAV 연동) 미구현 입력값 저장/로드 불가
동적 행 추가 미구현 검사 데이터 행 추가 불가
변수/매크로 시스템 미구현 자동 값 주입 불가
인쇄/PDF 출력 미구현 블록 문서 인쇄 불가
Columns 내부 블록 미구현 다단 레이아웃 활용 불가

결론: 양식 디자이너는 레이아웃 편집기로만 동작. 실제 문서 생성/결재/인쇄에서는 Legacy Builder만 사용 가능.


2. 목표

Legacy Builder의 모든 기능을 양식 디자이너에서 지원하면서, 더 유연하고 확장 가능한 문서 시스템 구축.

최종 목표:

양식 디자이너로 서식 설계
    ↓
블록 스키마 기반 문서 생성 (데이터 입력)
    ↓
결재 워크플로우 (작성 → 검토 → 승인)
    ↓
인쇄/PDF 출력
    ↓
Legacy Builder 완전 대체

3. 개발 로드맵 (6단계)

Phase 2: 블록 런타임 렌더러 (기반 인프라)

목표: 저장된 블록 스키마를 문서 생성/조회/인쇄에서 렌더링

2-1. 블록 렌더러 엔진

위치: mng/resources/views/documents/partials/block-renderer.blade.php

schema JSON 입력
    ↓
블록 타입별 Blade 컴포넌트 렌더링
    ↓
모드별 출력:
  - view 모드: 읽기 전용 HTML
  - edit 모드: 입력 폼 HTML
  - print 모드: 인쇄 최적화 HTML

핵심 구현:

// BlockRendererService
class BlockRendererService
{
    public function render(array $schema, string $mode, array $data = []): string
    {
        $html = '';
        foreach ($schema['blocks'] as $block) {
            $html .= $this->renderBlock($block, $mode, $data);
        }
        return $html;
    }

    private function renderBlock(array $block, string $mode, array $data): string
    {
        return match($block['type']) {
            'heading'        => $this->renderHeading($block, $mode),
            'paragraph'      => $this->renderParagraph($block, $mode),
            'table'          => $this->renderTable($block, $mode, $data),
            'text_field'     => $this->renderTextField($block, $mode, $data),
            'number_field'   => $this->renderNumberField($block, $mode, $data),
            'date_field'     => $this->renderDateField($block, $mode, $data),
            'select_field'   => $this->renderSelectField($block, $mode, $data),
            'checkbox_field' => $this->renderCheckboxField($block, $mode, $data),
            'textarea_field' => $this->renderTextareaField($block, $mode, $data),
            'signature_field'=> $this->renderSignatureField($block, $mode, $data),
            'divider'        => $this->renderDivider($block),
            'spacer'         => $this->renderSpacer($block),
            'columns'        => $this->renderColumns($block, $mode, $data),
            'approval_line'  => $this->renderApprovalLine($block, $mode, $data),
            'dynamic_table'  => $this->renderDynamicTable($block, $mode, $data),
            default          => '',
        };
    }
}

2-2. 문서 편집 화면 통합

수정 대상: mng/resources/views/documents/edit.blade.php

Template 로드
    ↓
isBlockBuilder() 체크
    ├── true  → BlockRendererService::render(schema, 'edit', data)
    └── false → 기존 Legacy 렌더링 (변경 없음)

2-3. 데이터 바인딩 (EAV 매핑)

블록의 binding 속성으로 EAV 데이터와 연결:

// 블록 스키마 예시
{
  "type": "text_field",
  "props": {
    "label": "제품명",
    "binding": "bf_product_name",  // ← EAV field_key
    "required": true
  }
}
저장 시:
  block.binding → document_data.field_key
  input.value   → document_data.field_value
  block.id      → document_data.section_id (블록 ID를 섹션으로 활용)

로드 시:
  document_data 조회 → field_key로 블록 매칭 → 값 주입

산출물:

파일 작업
mng/app/Services/BlockRendererService.php 신규 생성
mng/resources/views/documents/partials/block-renderer.blade.php 신규 생성
mng/resources/views/documents/edit.blade.php 블록 빌더 분기 추가
mng/resources/views/documents/show.blade.php 블록 빌더 분기 추가
api/app/Services/DocumentService.php 블록 데이터 저장/로드 로직

Phase 3: 결재선 블록

목표: 블록 스키마 내에서 결재 워크플로우 정의

3-1. approval_line 블록 타입 추가

스키마:

{
  "type": "approval_line",
  "props": {
    "steps": [
      { "role": "작성", "department": "", "name": "" },
      { "role": "검토", "department": "", "name": "" },
      { "role": "승인", "department": "", "name": "" }
    ],
    "style": "horizontal",
    "showStamp": true
  }
}

3-2. 팔레트에 결재선 블록 추가

// 블록 팔레트 추가
{ type: 'approval_line', icon: '✓', label: '결재선', category: '워크플로우' }

3-3. 속성 패널 결재선 편집기

┌─────────────────────────────┐
│ 결재선 설정                   │
│                             │
│ [+ 단계 추가]                │
│                             │
│ 1. 역할: [작성 ▼]            │
│    부서: [___________]       │
│    이름: [___________]       │
│                             │
│ 2. 역할: [검토 ▼]            │
│    부서: [___________]       │
│    이름: [___________]       │
│                             │
│ 3. 역할: [승인 ▼]            │
│    부서: [___________]       │
│    이름: [___________]       │
│                             │
│ ☐ 직인 표시                  │
│ 스타일: [가로형 ▼]            │
└─────────────────────────────┘

3-4. 문서 생성 시 결재 연동

블록 스키마 → approval_line 블록 추출
    ↓
DocumentApproval 레코드 자동 생성
    ↓
기존 결재 워크플로우 (submit → approve → reject) 그대로 활용

산출물:

파일 작업
block-editor.blade.php approval_line 블록 추가
block-canvas.blade.php 결재선 렌더링
BlockRendererService.php 결재선 view/edit/print 렌더
DocumentService.php 블록 결재선 → DocumentApproval 변환

Phase 4: 동적 테이블 블록 + 변수 시스템

목표: 문서 작성 시 행 추가/삭제 가능한 테이블 + 자동 값 주입

4-1. dynamic_table 블록 타입

기존 table 블록은 정적 (양식 설계 시 행 고정). dynamic_table은 문서 작성 시 행 동적 추가.

스키마:

{
  "type": "dynamic_table",
  "props": {
    "label": "검사 데이터",
    "columns": [
      { "key": "col_item", "label": "항목", "type": "text", "width": 120 },
      { "key": "col_standard", "label": "기준값", "type": "text", "width": 100 },
      { "key": "col_measured", "label": "측정값", "type": "number", "width": 100 },
      { "key": "col_result", "label": "판정", "type": "select",
        "options": ["합격", "불합격", "보류"], "width": 80 }
    ],
    "minRows": 1,
    "maxRows": 50,
    "initialRows": 3,
    "showRowNumber": true,
    "binding": "inspection_data"
  }
}

4-2. EAV 데이터 매핑

dynamic_table 블록 데이터 저장:

document_data 레코드:
  section_id  = (dynamic_table 블록 ID → section 매핑)
  column_id   = (columns[].key → column 매핑)
  row_index   = 0, 1, 2, ...
  field_key   = "col_item", "col_standard", ...
  field_value = 입력값

4-3. 변수/매크로 시스템

내장 변수:

변수 설명
{{today}} 현재 날짜 YYYY-MM-DD
{{now}} 현재 시각 YYYY-MM-DD HH:mm
{{user.name}} 로그인 사용자명
{{user.department}} 로그인 사용자 부서
{{doc.number}} 문서 번호 자동채번
{{doc.title}} 문서 제목
{{template.company}} 서식 회사명

연결 데이터 변수 (linked data):

변수 설명
{{item.name}} 연결 품목명
{{item.code}} 연결 품목 코드
{{order.number}} 연결 작업지시서 번호
{{order.quantity}} 연결 수량

변수 사용 예시 (블록 속성):

{
  "type": "text_field",
  "props": {
    "label": "검사일자",
    "binding": "bf_inspection_date",
    "default": "{{today}}"
  }
}
{
  "type": "paragraph",
  "props": {
    "text": "작성자: {{user.name}} ({{user.department}})"
  }
}

4-4. 변수 해석 엔진

// VariableResolver
class VariableResolver
{
    public function resolve(string $text, array $context): string
    {
        return preg_replace_callback('/\{\{(\w+(?:\.\w+)*)\}\}/', function ($m) use ($context) {
            return data_get($context, $m[1], $m[0]);
        }, $text);
    }

    public function buildContext(Document $document, ?User $user = null): array
    {
        return [
            'today' => now()->format('Y-m-d'),
            'now'   => now()->format('Y-m-d H:i'),
            'user'  => [
                'name'       => $user?->name,
                'department' => $user?->department?->name,
            ],
            'doc'   => [
                'number' => $document->document_number,
                'title'  => $document->title,
            ],
            'item'  => $this->resolveLinkedItem($document),
            'order' => $this->resolveLinkedOrder($document),
        ];
    }
}

산출물:

파일 작업
block-editor.blade.php dynamic_table 블록 추가
BlockRendererService.php 동적 테이블 렌더링 (edit: 행 추가/삭제 UI)
mng/app/Services/VariableResolver.php 신규 생성
DocumentService.php 동적 테이블 EAV 저장/로드

Phase 5: 고급 블록 + 조건부 로직

목표: 수식 계산, 조건부 표시, 이미지 블록 등 고급 기능

5-1. 수식 블록 (formula)

{
  "type": "formula_field",
  "props": {
    "label": "합계",
    "expression": "SUM(inspection_data.col_measured)",
    "format": "number",
    "decimal": 2
  }
}

지원 함수:

함수 설명 예시
SUM() 합계 SUM(table.col_amount)
AVG() 평균 AVG(table.col_measured)
COUNT() 개수 COUNT(table.col_item)
MIN() / MAX() 최솟값/최댓값 MIN(table.col_value)
IF() 조건 IF(AVG(table.col_measured) > 5, "합격", "불합격")
ROUND() 반올림 ROUND(AVG(table.col_measured), 2)

5-2. 조건부 표시 (conditional visibility)

모든 블록에 visibility 속성 추가:

{
  "type": "paragraph",
  "props": {
    "text": "불합격 사유를 기재해 주세요.",
    "visibility": {
      "condition": "field",
      "field": "bf_judgement",
      "operator": "equals",
      "value": "불합격"
    }
  }
}

연산자:

연산자 설명
equals 같으면 표시
not_equals 다르면 표시
contains 포함하면 표시
greater_than 크면 표시
less_than 작으면 표시
is_empty 비어있으면 표시
is_not_empty 비어있지 않으면 표시

5-3. 이미지 블록

{
  "type": "image",
  "props": {
    "label": "검사 사진",
    "source": "upload",
    "maxSize": 10,
    "accept": ["jpeg", "png", "webp"],
    "width": "100%",
    "align": "center"
  }
}

5-4. Columns 내부 블록 (중첩 렌더링)

{
  "type": "columns",
  "props": {
    "count": 2,
    "ratio": "1:1",
    "children": [
      [
        { "type": "text_field", "props": { "label": "품명" } },
        { "type": "date_field", "props": { "label": "검사일" } }
      ],
      [
        { "type": "text_field", "props": { "label": "LOT NO" } },
        { "type": "select_field", "props": { "label": "판정", "options": ["합격","불합격"] } }
      ]
    ]
  }
}

산출물:

파일 작업
block-editor.blade.php formula, image, conditional 블록 추가
mng/app/Services/FormulaEngine.php 수식 해석 엔진
BlockRendererService.php 조건부 표시, 수식 계산 렌더링

Phase 6: 인쇄/PDF + Legacy 대체

목표: 블록 문서 인쇄 완성, Legacy Builder 완전 대체

6-1. 인쇄 레이아웃

print 모드 렌더링:
  - 페이지 설정 (A4/A3) 적용
  - 여백 적용
  - 폼 필드 → 값 표시 (입력란 제거)
  - 서명 → 서명 이미지 표시
  - 결재선 → 직인 표시
  - 페이지 넘김 (page-break) 자동 계산

6-2. PDF 내보내기

블록 렌더러 (print 모드 HTML)
    ↓
Puppeteer / wkhtmltopdf
    ↓
PDF 파일 생성
    ↓
다운로드 또는 첨부

6-3. Legacy → Block 마이그레이션 도구

기존 Legacy 서식을 Block 스키마로 자동 변환:

// LegacyToBlockMigrator
class LegacyToBlockMigrator
{
    public function convert(DocumentTemplate $legacy): array
    {
        $blocks = [];

        // 1. 결재선 → approval_line 블록
        if ($legacy->approvalLines->isNotEmpty()) {
            $blocks[] = $this->convertApprovalLines($legacy->approvalLines);
        }

        // 2. 기본필드 → text_field / date_field 블록
        foreach ($legacy->basicFields as $field) {
            $blocks[] = $this->convertBasicField($field);
        }

        // 3. 섹션 → heading + image 블록
        foreach ($legacy->sections as $section) {
            $blocks[] = ['type' => 'heading', 'props' => ['text' => $section->title]];
            if ($section->image_path) {
                $blocks[] = ['type' => 'image', 'props' => ['source' => $section->image_path]];
            }
        }

        // 4. 컬럼 → dynamic_table 블록
        if ($legacy->columns->isNotEmpty()) {
            $blocks[] = $this->convertColumns($legacy->columns);
        }

        return [
            'version' => '1.0',
            'page' => ['size' => 'A4', 'orientation' => 'portrait'],
            'blocks' => $blocks,
        ];
    }
}

6-4. Legacy Builder 비활성화

Phase 6 완료 후:
  - 새 양식 생성: 양식 디자이너만 허용
  - 기존 Legacy 서식: 조회/편집 가능 (변환 유도)
  - Legacy Builder "새 양식" 버튼: "양식 디자이너" 사용 안내

산출물:

파일 작업
mng/resources/views/documents/print-block.blade.php 인쇄 전용 뷰
mng/app/Services/LegacyToBlockMigrator.php 변환 도구
mng/app/Services/PdfExportService.php PDF 생성

4. Phase별 우선순위 및 의존관계

Phase 2: 블록 런타임 렌더러 ──────────────────────┐
  (렌더러 엔진, 데이터 바인딩, 문서 편집 통합)      │
                                                  │
Phase 3: 결재선 블록 ─────────────────────┐        │
  (approval_line 블록, 결재 워크플로우)     │        │
                                         ↓        ↓
Phase 4: 동적 테이블 + 변수 ──────→ Phase 5: 고급 블록
  (dynamic_table, 매크로)           (수식, 조건부, 이미지)
                                         │
                                         ↓
                                  Phase 6: 인쇄/PDF + Legacy 대체
                                    (마이그레이션 도구)
Phase 의존 난이도 예상 범위
Phase 2 없음 (기반) 높음 렌더러 엔진 + EAV 매핑
Phase 3 Phase 2 중간 결재선 블록 + 워크플로우 연동
Phase 4 Phase 2 높음 동적 테이블 + 변수 해석
Phase 5 Phase 4 높음 수식 엔진 + 조건부 로직
Phase 6 Phase 3~5 중간 인쇄 + 마이그레이션

5. 스키마 버전 관리

5.1 버전 규칙

버전 Phase 변경 내용
1.0 Phase 1 (현재) 기본 13개 블록
2.0 Phase 2~3 데이터 바인딩, approval_line 추가
3.0 Phase 4 dynamic_table, 변수 시스템
4.0 Phase 5 formula, conditional, image

5.2 하위 호환

{
  "version": "3.0",
  "page": { ... },
  "blocks": [ ... ],
  "variables": { ... },
  "migrations": {
    "from_1.0": "auto"
  }
}
  • 이전 버전 스키마 자동 인식 및 업그레이드
  • 신규 블록 타입은 이전 버전에서 무시 (graceful degradation)

6. 신규 블록 타입 전체 목록

Phase별 블록 추가 계획

Phase 블록 타입 카테고리 설명
1 (완료) heading 기본 제목
1 (완료) paragraph 기본 문단
1 (완료) table 기본 정적 테이블
1 (완료) columns 기본 다단 레이아웃
1 (완료) divider 기본 구분선
1 (완료) spacer 기본 여백
1 (완료) text_field 텍스트 입력
1 (완료) number_field 숫자 입력
1 (완료) date_field 날짜 입력
1 (완료) select_field 드롭다운
1 (완료) checkbox_field 체크박스
1 (완료) textarea_field 장문 텍스트
1 (완료) signature_field 서명
3 approval_line 워크플로우 결재선
4 dynamic_table 데이터 동적 행 테이블
5 formula_field 데이터 수식 계산
5 image 미디어 이미지 업로드/표시

7. 기술 스택 정리

구성 요소 기술 비고
블록 에디터 UI Alpine.js + Blade 기존 유지
드래그-앤-드롭 SortableJS 기존 유지
블록 렌더러 PHP (BlockRendererService) 신규
변수 해석 PHP (VariableResolver) 신규
수식 엔진 PHP (FormulaEngine) 신규
데이터 저장 EAV (document_data) 기존 테이블 활용
결재 워크플로우 DocumentApproval 기존 로직 활용
인쇄 CSS @media print 신규
PDF Puppeteer 또는 wkhtmltopdf 신규

8. 위험 요소 및 대응

위험 영향 대응
EAV 매핑 복잡도 블록 ID ↔ section_id 매핑 불일치 블록 ID를 section 대용으로 사용, 매핑 테이블 추가 검토
Legacy 데이터 호환 기존 문서 데이터 접근 불가 Legacy 서식 문서는 기존 방식 유지, 신규 서식만 블록 적용
수식 엔진 보안 임의 코드 실행 위험 화이트리스트 함수만 허용, eval 사용 금지
인쇄 레이아웃 브라우저별 차이 CSS @page 규격 준수, PDF 변환 권장
스키마 마이그레이션 버전 업그레이드 시 데이터 손실 하위 호환 보장, 자동 업그레이드 로직

9. 성공 기준

기준 측정 방법
블록 서식으로 문서 생성 가능 Phase 2 완료 후 테스트
결재 워크플로우 정상 동작 Phase 3 완료 후 테스트
동적 행 추가/삭제 Phase 4 완료 후 테스트
변수 자동 주입 Phase 4 완료 후 테스트
Legacy 서식 자동 변환 Phase 6 완료 후 테스트
인쇄 품질 A4 기준 정상 Phase 6 완료 후 테스트

관련 문서


최종 업데이트: 2026-03-06