diff --git a/plans/phase4-approval-integration-plan.md b/plans/phase4-approval-integration-plan.md new file mode 100644 index 0000000..11eb221 --- /dev/null +++ b/plans/phase4-approval-integration-plan.md @@ -0,0 +1,494 @@ +# Phase 4.2 — 결재 연동 (Document ↔ Approval 브릿지) + +> **작성일**: 2026-02-27 +> **목적**: 검사 문서(Document)를 기존 결재 시스템(Approval)에 연동하여 /approval/inbox에서 결재 처리 가능하게 함 +> **상위 문서**: [`integrated-master-plan.md`](./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` | ⏳ | + +**마이그레이션 상세:** +```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 핵심 로직:** +```php +// 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 핵심 로직:** +```php +// 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 핵심:** +```php +// 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 문서 유형 필터:** +```typescript +// ApprovalType에 추가 +type ApprovalType = 'expense_estimate' | 'expense_report' | 'proposal' | 'document'; + +// 필터 옵션 추가 +{ value: 'document', label: '문서 결재' } +``` + +**5.2 문서 렌더링:** +```typescript +// DocumentDetailModalV2 내 renderDocument() 확장 +case 'document': + return ( + + } + /> + + + ); +``` + +### 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/plans/integrated-master-plan.md` +- **Phase 3 상세**: `docs/plans/integrated-phase-3.md` +- **품질 체크리스트**: `QUALITY_CHECKLIST.md` + +--- + +## 8. 세션 관리 + +### 세션 시작 시 +``` +1. 이 문서 읽기 → 현재 진행 상태 확인 +2. 마지막 완료 Step 확인 → 다음 Step 진행 +``` + +### 작업 중 +``` +각 Step 완료 시 → 상태 ⏳→✅ 업데이트 +컨펌 필요 시 → 사용자 확인 +``` + +--- + +*이 문서는 /plan 스킬 기반으로 생성되었습니다.*