Files
sam-docs/dev/dev_plans/phase4-approval-integration-plan.md
권혁성 db63fcff85 refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동)
- 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/)
- 기획팀 폴더 requests/ 생성
- plans/ → dev/dev_plans/ 이름 변경
- README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용)
- resources.md 신규 (노션 링크용, assets/brochure 이관 예정)
- CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동
- 전체 참조 경로 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:46:03 +09:00

17 KiB

Phase 4.2 — 결재 연동 (Document ↔ Approval 브릿지)

작성일: 2026-02-27 목적: 검사 문서(Document)를 기존 결재 시스템(Approval)에 연동하여 /approval/inbox에서 결재 처리 가능하게 함 상위 문서: integrated-master-plan.md Phase 4 상태: 🔄 진행중


📍 현재 진행 상태

항목 내용
마지막 완료 작업 Step 6: Frontend — 문서 상세에서 결재 상신 기능
다음 작업 전체 완료
진행률 6/6 (100%)
마지막 업데이트 2026-02-27

1. 개요

1.1 배경

현재 SAM에는 두 개의 독립 시스템이 존재:

시스템 용도 테이블 UI
Document 검사 성적서, 작업일지 저장 documents, document_approvals, document_data 검사 화면에서 생성
Approval 결재 워크플로우 approvals, approval_steps /approval/inbox 등 완성

문제: 검사 문서 작성 후 결재 워크플로우가 없음. /approval/inbox에 검사 문서가 나타나지 않음.

1.2 목표

검사 데이터 입력 → Document 저장 (DRAFT)
    ↓
"결재 상신" → Approval 자동 생성 (PENDING) + 결재 단계 생성
    ↓
/approval/inbox 리스트에 표시
    ↓
결재자 클릭 → 검사 성적서 렌더링 (FqcDocumentContent + ApprovalLineBox)
    ↓
승인/반려 → Document 상태 동기화 + 결재란에 승인자 이름 반영

1.3 범위

  • 대상: 모든 문서 유형 (중간검사 4종 + 작업일지 3종 + 제품검사 + 수입검사)
  • 방식: Document 상신 시 Approval 자동 생성 (브릿지 패턴)
  • UI: 기존 /approval/inbox + DocumentDetailModalV2 활용

1.4 변경 승인 정책

분류 예시 승인
즉시 가능 필드 추가, 시더, 프론트 타입 추가 불필요
⚠️ 컨펌 필요 approvals 테이블 컬럼 추가, 서비스 로직 변경 필수
🔴 금지 기존 Document/Approval 동작 변경 별도 협의

2. 현재 아키텍처 분석

2.1 Document 시스템

documents (23건, 전부 DRAFT)
├── template_id → document_templates (27종)
├── document_approvals (step 순차 결재)
│   ├── user_id + step + role(작성/검토/승인) + status
│   └── 순서대로 진행 → 모두 승인 시 Document APPROVED
├── document_data (EAV 패턴)
└── linkable_type/linkable_id (다형성 참조)

상태: DRAFT → PENDING → APPROVED | REJECTED | CANCELLED

2.2 Approval 시스템

approvals (30건: pending 14, approved 8, draft 6, rejected 2)
├── form_id → approval_forms (4종: 품의서, 지출결의서, 비용견적서)
├── approval_steps (step_order 순차)
│   ├── approver_id + step_type(approval/agreement/reference) + status
│   └── 현재 차례만 처리 가능
├── content (JSON - 비정형 데이터)
└── drafter_id (기안자)

상태: draft → pending → approved | rejected | cancelled

2.3 Template 결재선 현황 (DB 확인)

template_id 이름 결재선
57 조인트바 중간검사 작성(판매) → 검토(생산) → 승인(품질/QC)
58 슬랫 중간검사 작성(판매) → 검토(생산) → 승인(품질/QC)
59 스크린 중간검사 작성(판매) → 검토(생산) → 승인(품질/QC)
60 절곡품 중간검사 작성(판매) → 검토(생산) → 승인(품질/QC)
62 스크린 작업일지 작성(판매/전진) → 검토(생산) → 승인(품질/QC)
63 슬랫 작업일지 작성(생산) → 승인 → 승인 → 승인 (4단계)
64 절곡 작업일지 작성(생산) → 승인 → 승인 → 승인 (4단계)
65 제품검사 성적서 작성(품질) → 검토(품질/QC) → 승인(경영/대표)

※ 수입검사 일부(id=6, 19~30)는 결재선 미설정


3. 브릿지 설계

3.1 아키텍처

┌─────────────────────────────────────────────────────────┐
│  Document System (데이터)     Approval System (워크플로우)  │
│                                                          │
│  documents ─────────────── approvals                     │
│  (검사 데이터, EAV)    ←→   (linkable_type=Document)      │
│                        브릿지  (linkable_id=document.id)  │
│  document_approvals         approval_steps               │
│  (결재란 표시용)       ←동기화→ (실제 결재 진행)              │
│                                                          │
│  document_templates         approval_forms               │
│  (양식 구조 정의)           (결재 양식: 'document')         │
└─────────────────────────────────────────────────────────┘

3.2 데이터 흐름

[Document 생성]
  └─ DocumentService::create()
     ├─ documents 레코드 생성 (DRAFT)
     ├─ document_approvals 생성 (user_id + step + role)
     └─ document_data 저장 (EAV)

[결재 상신] ← 새로 구현
  └─ DocumentService::submit()
     ├─ Document.status = PENDING
     ├─ Approval 자동 생성 ← NEW
     │   ├─ form_id = 'document' form
     │   ├─ linkable_type = 'App\Models\Documents\Document'
     │   ├─ linkable_id = document.id
     │   ├─ title = document.title
     │   ├─ content = { document_id, template_id, document_no }
     │   └─ status = 'pending'
     └─ ApprovalSteps 생성 ← NEW
         └─ document_approvals → approval_steps 매핑
            ├─ user_id → approver_id
            ├─ step → step_order
            └─ role → step_type (작성=approval, 검토=approval, 승인=approval)

[결재함 표시] ← 기존 동작 활용
  └─ ApprovalService::inbox()
     └─ 기존 쿼리에 자동 포함 (approvals 테이블이므로)

[결재 처리] ← 후속 동기화 추가
  └─ ApprovalService::approve() / reject()
     ├─ approval_steps 상태 변경 (기존)
     └─ 후크: linkable_type=Document인 경우 ← NEW
         ├─ document_approvals 해당 step 동기화
         ├─ Document.status 동기화
         └─ 승인자 이름 반영 (acted_at 기록)

[문서 렌더링] ← 프론트 확장
  └─ DocumentDetailModalV2
     ├─ approval.linkable_type === 'Document' 감지
     ├─ Document + Template 데이터 로딩
     └─ FqcDocumentContent + ApprovalLineBox 렌더링

3.3 role → step_type 매핑

document_approvals.role approval_steps.step_type 비고
작성 / writer / 담당자 approval 첫 단계 (작성자 확인)
검토 / reviewer approval 중간 단계
승인 / approver / 부서장 / QC / 대표 approval 최종 단계

현재 reference(참조) 역할은 document_template_approval_lines에 미사용 → 추후 필요 시 확장


4. 작업 항목

Step 1: DB 마이그레이션

# 작업 파일 상태
1.1 approvals 테이블에 linkable_type, linkable_id 컬럼 추가 migrations/xxxx_add_linkable_to_approvals.php
1.2 approval_forms에 문서 결재용 양식 시더 seeders/DocumentApprovalFormSeeder.php

마이그레이션 상세:

// 1.1 approvals 테이블
Schema::table('approvals', function (Blueprint $table) {
    $table->string('linkable_type')->nullable()->after('attachments');
    $table->unsignedBigInteger('linkable_id')->nullable()->after('linkable_type');
    $table->index(['linkable_type', 'linkable_id']);
});

// 1.2 approval_forms 시더
ApprovalForm::create([
    'tenant_id' => $tenantId,
    'name' => '문서 결재',
    'code' => 'document',
    'category' => '문서',
    'template' => json_encode([]),  // 문서 자체가 양식
    'is_active' => true,
]);

Step 2: Backend — Document → Approval 브릿지

# 작업 파일 상태
2.1 DocumentService::submit() 수정 — Approval 자동 생성 app/Services/DocumentService.php
2.2 Approval 모델에 linkable 관계 추가 app/Models/Tenants/Approval.php
2.3 Document API에 결재 상태 포함 DocumentService::show()

2.1 핵심 로직:

// DocumentService::submit() 내부에 추가
public function submit(int $id): Document
{
    $document = Document::findOrFail($id);
    // ... 기존 검증 로직 ...

    $document->update(['status' => Document::STATUS_PENDING, 'submitted_at' => now()]);

    // === NEW: Approval 자동 생성 ===
    $this->createApprovalBridge($document);

    return $document;
}

private function createApprovalBridge(Document $document): void
{
    $form = ApprovalForm::where('code', 'document')
        ->where('tenant_id', $document->tenant_id)
        ->first();

    if (!$form) return; // 양식 미등록 시 스킵

    $approval = Approval::create([
        'tenant_id' => $document->tenant_id,
        'document_number' => $document->document_no,
        'form_id' => $form->id,
        'title' => $document->title,
        'content' => [
            'document_id' => $document->id,
            'template_id' => $document->template_id,
            'document_no' => $document->document_no,
        ],
        'status' => Approval::STATUS_PENDING,
        'drafter_id' => $document->created_by,
        'drafted_at' => now(),
        'current_step' => 1,
        'linkable_type' => Document::class,
        'linkable_id' => $document->id,
    ]);

    // document_approvals → approval_steps 변환
    $docApprovals = $document->approvals()
        ->orderBy('step')
        ->get();

    foreach ($docApprovals as $docApproval) {
        ApprovalStep::create([
            'approval_id' => $approval->id,
            'step_order' => $docApproval->step,
            'step_type' => ApprovalLine::STEP_TYPE_APPROVAL,
            'approver_id' => $docApproval->user_id,
            'status' => ApprovalStep::STATUS_PENDING,
        ]);
    }
}

Step 3: Backend — Approval → Document 동기화

# 작업 파일 상태
3.1 ApprovalService::approve() 후크 — Document 동기화 app/Services/ApprovalService.php
3.2 ApprovalService::reject() 후크 — Document 동기화 app/Services/ApprovalService.php
3.3 ApprovalService::cancel() 후크 — Document 동기화 app/Services/ApprovalService.php

3.1 핵심 로직:

// ApprovalService::approve() 내부, 기존 로직 뒤에 추가
private function syncToDocument(Approval $approval): void
{
    if ($approval->linkable_type !== Document::class) return;

    $document = Document::find($approval->linkable_id);
    if (!$document) return;

    // approval_steps → document_approvals 동기화
    foreach ($approval->steps as $step) {
        $docApproval = $document->approvals()
            ->where('step', $step->step_order)
            ->first();

        if ($docApproval && $step->status !== ApprovalStep::STATUS_PENDING) {
            $docApproval->update([
                'status' => strtoupper($step->status), // pending→PENDING, approved→APPROVED
                'acted_at' => $step->acted_at,
                'comment' => $step->comment,
            ]);
        }
    }

    // Document 전체 상태 동기화
    $documentStatus = match ($approval->status) {
        'approved' => Document::STATUS_APPROVED,
        'rejected' => Document::STATUS_REJECTED,
        'cancelled' => Document::STATUS_CANCELLED,
        default => Document::STATUS_PENDING,
    };

    $document->update([
        'status' => $documentStatus,
        'completed_at' => in_array($approval->status, ['approved', 'rejected']) ? now() : null,
    ]);
}

Step 4: Backend — Document 조회 API 확장

# 작업 파일 상태
4.1 Approval show() — linkable 문서 데이터 포함 app/Services/ApprovalService.php
4.2 Document API 응답에 approval_id 포함 DocumentResource 또는 show()

4.1 핵심:

// ApprovalService::show() 내부
// 기존 with() 관계에 추가
$approval->load(['steps.approver', 'form']);

// linkable이 Document인 경우 문서 데이터 포함
if ($approval->linkable_type === Document::class) {
    $approval->loadMissing('linkable.template', 'linkable.data', 'linkable.approvals.user');
}

Step 5: Frontend — /approval/inbox 확장

# 작업 파일 상태
5.1 ApprovalBox 문서 유형 필터 추가 components/approval/ApprovalBox/index.tsx
5.2 DocumentDetailModalV2에 document 렌더링 case 추가 components/approval/DocumentDetailModalV2.tsx
5.3 Approval actions에 문서 조회 함수 추가 components/approval/ApprovalBox/actions.ts
5.4 타입 정의 확장 types 또는 해당 파일

5.1 문서 유형 필터:

// ApprovalType에 추가
type ApprovalType = 'expense_estimate' | 'expense_report' | 'proposal' | 'document';

// 필터 옵션 추가
{ value: 'document', label: '문서 결재' }

5.2 문서 렌더링:

// DocumentDetailModalV2 내 renderDocument() 확장
case 'document':
  return (
    <DocumentWrapper>
      <DocumentHeader
        title={linkedDocument.template.title}
        customApproval={<ApprovalLineBox approvals={approvalLineData} />}
      />
      <FqcDocumentContent
        ref={contentRef}
        templateData={linkedDocument.template}
        documentData={linkedDocument.data}
        readonly={true}
      />
    </DocumentWrapper>
  );

Step 6: Frontend — 문서 상세에서 상신 기능

# 작업 파일 상태
6.1 문서 상세 화면에 "결재 상신" 버튼 InspectionReportModal.tsx
6.2 상신 API 호출 (DocumentService submit) production/WorkOrders/actions.ts

※ 현재 문서가 모두 DRAFT 상태이므로, 상신 버튼이 동작하면 바로 /approval/inbox에 표시됨


5. 수정 파일 목록

Backend (api/)

파일 변경 내용
database/migrations/xxxx_add_linkable_to_approvals.php 신규 — linkable 컬럼 추가
database/seeders/DocumentApprovalFormSeeder.php 신규 — 문서 결재 양식 시더
app/Models/Tenants/Approval.php linkable 관계 추가, fillable 확장
app/Services/DocumentService.php submit()에 브릿지 로직 추가
app/Services/ApprovalService.php approve/reject/cancel에 동기화 후크

Frontend (react/)

파일 변경 내용
components/approval/ApprovalBox/index.tsx 문서 유형 필터 추가
components/approval/ApprovalBox/actions.ts 문서 데이터 조회 함수
components/approval/DocumentDetailModalV2.tsx document case 렌더링 추가
타입 파일 ApprovalType 확장, LinkedDocument 타입

6. 검증 시나리오

T1: 브릿지 생성 확인

검사 문서 생성 (DRAFT) → submit() 호출
→ approvals 레코드 생성됨 (linkable_type=Document, status=pending)
→ approval_steps 생성됨 (document_approvals 기반)

T2: 결재함 표시 확인

/approval/inbox 접속
→ 문서 결재 항목이 리스트에 표시됨
→ 문서유형 필터 '문서 결재' 적용 시 필터됨

T3: 문서 렌더링 확인

결재함에서 항목 클릭
→ DocumentDetailModalV2 열림
→ 검사 성적서 내용 표시 (FqcDocumentContent)
→ 결재란 표시 (ApprovalLineBox)

T4: 승인 동기화 확인

결재자가 승인 처리
→ approval_steps 상태 approved
→ document_approvals 해당 step 동기화 (status=APPROVED, acted_at)
→ 모든 단계 완료 시 documents.status=APPROVED

T5: 반려 동기화 확인

결재자가 반려 처리
→ approval.status = rejected
→ documents.status = REJECTED
→ document_approvals 해당 step 동기화

T6: 결재란 승인자 표시

승인 완료 후 문서 보기
→ ApprovalLineBox에 승인자 이름, 시각 표시
→ 각 단계별 상태 아이콘 (✓/✗/⏱)

7. 참고 문서

  • 마스터 플랜: docs/dev_plans/integrated-master-plan.md
  • Phase 3 상세: docs/dev_plans/integrated-phase-3.md
  • 품질 체크리스트: QUALITY_CHECKLIST.md

8. 세션 관리

세션 시작 시

1. 이 문서 읽기 → 현재 진행 상태 확인
2. 마지막 완료 Step 확인 → 다음 Step 진행

작업 중

각 Step 완료 시 → 상태 ⏳→✅ 업데이트
컨펌 필요 시 → 사용자 확인

이 문서는 /plan 스킬 기반으로 생성되었습니다.