Files
sam-docs/dev/dev_plans/approval-system-unification-plan.md
김보곤 764ef9f82a docs: [approval] 결재관리 통합 계획서 현행화
- P1~P4 완료 상태 반영 (설계 중 → P1~P4 완료)
- 현재 구현 현황 (45 라우트, 48 서비스 메서드) 추가
- API/MNG 기능 비교표를 실제 코드 기준으로 갱신
- 남은 작업: P5(Leave 연동), P6(테스트), 병렬결재/위임 inbox 검증
2026-03-11 19:40:29 +09:00

23 KiB

결재관리 시스템 통합 계획서 — MNG 로직을 API로 통합

작성일: 2026-03-11 상태: P1P4 완료 / P5P6 미착수 담당: R&D실 관련 문서: phase4-approval-integration-plan.md (Document↔Approval 브릿지, 완료)


1. 개요

1.1 배경

MNG와 API에 이중 구현되어 있던 결재관리 시스템을 API로 통합하는 작업이다. P1~P4는 완료되어 API가 MNG와 동등한 기능을 제공한다.

항목 MNG (관리자 패널) API (REST API) 비고
결재 흐름 상신/승인/반려/회수/보류/전결/복사재기안 동일 P2에서 완료
위임 시스템 완전 구현 CRUD 구현 P4에서 완료
병렬 결재 parallel_group ⚠️ 컬럼 존재, 로직 미확인 검증 필요
결재자 스냅샷 이름/부서/직급 구현 완료 P2에서 완료
대결 처리 acted_by ⚠️ 컬럼 존재, 위임 inbox 통합 미확인 검증 필요
반려 이력 rejection_history 구현 완료 P2에서 완료
뱃지 카운트 4종 뱃지 구현 완료 P2에서 완료
양식별 뷰 27개 Blade 파일 (React 측 담당) 범위 외
Leave 연동 결재 완료→휴가 자동 생성 미구현 P5 미착수
Document 동기화 syncToLinkedDocument() API 고유
현황 요약 API draftsSummary(), inboxSummary() API 고유

MNG에서 코드브릿지엑스(tenant_id=1)만 사용하던 결재 시스템을 모든 테넌트가 API를 통해 동일하게 사용할 수 있도록 통합한다.

1.2 목표

  1. API에 MNG의 고급 결재 기능을 모두 이식 — 보류/전결/위임/병렬결재/스냅샷/뱃지
  2. MNG는 자체 서비스 유지 — MNG는 HTMX 기반 관리자 패널로, API 호출이 아닌 자체 서비스를 사용 (기존 동작 유지)
  3. React(사용자 앱)에서 API를 통해 전체 결재 기능 사용 가능
  4. 기존 데이터/동작에 영향 없음 — 하위 호환성 100% 유지

1.3 핵심 원칙

✅ API 모델은 MNG 모델의 상위 호환이 되어야 한다
✅ DB 마이그레이션은 API 프로젝트에서만 관리
✅ MNG의 검증된 비즈니스 로직을 API에 이식
✅ 기존 API 엔드포인트의 요청/응답 호환성 유지
❌ MNG의 Blade 뷰 시스템은 이식 대상 아님 (React에서 별도 구현)
❌ e-Sign(전자서명)은 이번 범위 제외 (별도 프로젝트)

2. 현재 상태 (2026-03-11 기준)

2.1 구현 현황 요약

항목 수량 상태
API 라우트 45개 전체 등록 완료
ApprovalController 메서드 24개 전체 구현
ApprovalService public 메서드 48개 전체 구현
FormRequest 클래스 6개 (Approval) + 2개 (Delegation) 생성 완료
TODO/FIXME 잔존 0건

2.2 DB 테이블 현황

모든 테이블은 API 마이그레이션으로 생성 완료. API 모델에서도 활용한다.

테이블 핵심 컬럼 DB 존재 API 활용 MNG 활용
approvals line_id, body, is_urgent, department_id
drafter_read_at, resubmit_count, rejection_history
recall_reason, parent_doc_id
linkable_type/id
approval_steps tenant_id, deleted_at
parallel_group, acted_by ⚠️
approver_name/department/position
approval_type
approval_forms body_template
approval_delegations 전체

2026-03-11 추가 마이그레이션: approval_steps 테이블에 tenant_id + deleted_at 컬럼 추가 (BelongsToTenant, SoftDeletes 적용)

2.3 API 서비스 기능 현황

기능 API MNG 상태
기안함/결재함/참조함/완료함 조회 완료
문서 CRUD 완료
상신/승인/반려/회수 완료
보류 / 보류 해제 완료
전결 (preDecide) 완료
복사 재기안 (copyForRedraft) 완료
뱃지 카운트 (badgeCounts) 완료
완료함 일괄 읽음 완료
결재자 스냅샷 저장 완료
반려 후 재상신 이력 완료
위임 CRUD 완료
현황 요약 (summary) API 고유
참조 미열람 (markUnread) API 고유
Document 동기화 API 고유
병렬 결재 (parallel_group) ⚠️ 검증 필요
위임 inbox 통합 (대결 처리) ⚠️ 검증 필요
Leave 연동 P5 미착수

3. 작업 범위 및 단계

3.1 전체 단계 요약

Phase 작업 상태 비고
P1 API 모델 확장 완료 모델 4개 수정/생성, ApprovalStep에 tenant_id+SoftDeletes 추가
P2 API 서비스 — 핵심 워크플로우 이식 완료 48개 public 메서드 구현
P3 API 엔드포인트 추가 완료 45개 라우트, 24개 컨트롤러 메서드, FormRequest 6개
P4 위임(Delegation) 시스템 이식 완료 CRUD 구현, FormRequest 2개
P5 Leave 연동 이식 미착수 결재 완료→휴가 자동 생성
P6 테스트 및 검증 미착수 병렬 결재, 위임 inbox 통합 검증 포함

4. Phase 1 — API 모델 확장 완료

4.1 Approval 모델 (api/app/Models/Tenants/Approval.php)

4.1.1 상태 상수 추가

// 기존
const STATUS_DRAFT = 'draft';
const STATUS_PENDING = 'pending';
const STATUS_APPROVED = 'approved';
const STATUS_REJECTED = 'rejected';
const STATUS_CANCELLED = 'cancelled';

// 추가
const STATUS_ON_HOLD = 'on_hold';

4.1.2 fillable 필드 확인

현재 API 모델의 $fillable에 누락된 필드 추가:

// 추가 대상 (DB에 이미 존재하는 컬럼)
'line_id', 'body', 'is_urgent', 'department_id',
'drafter_read_at', 'resubmit_count', 'rejection_history',
'recall_reason', 'parent_doc_id'

4.1.3 casts 확장

// 추가
'rejection_history' => 'array',
'is_urgent' => 'boolean',
'drafter_read_at' => 'datetime',

4.1.4 관계 추가

public function line(): BelongsTo
{
    return $this->belongsTo(ApprovalLine::class, 'line_id');
}

public function parentDocument(): BelongsTo
{
    return $this->belongsTo(Approval::class, 'parent_doc_id');
}

public function childDocuments(): HasMany
{
    return $this->hasMany(Approval::class, 'parent_doc_id');
}

public function department(): BelongsTo
{
    return $this->belongsTo(Department::class, 'department_id');
}

4.1.5 헬퍼 메서드 추가 (MNG에서 이식)

public function isHoldable(): bool     // pending 상태에서 보류 가능
public function isHoldReleasable(): bool // on_hold 상태에서 해제 가능
public function isCopyable(): bool     // 완료/반려/회수 상태에서 복사 가능
public function isDeletableBy(?User $user): bool // 특정 사용자가 삭제 가능한지
public function getStatusColorAttribute(): string // 상태별 UI 색상

4.1.6 스코프 추가

public function scopeOnHold($query) // on_hold 상태 필터

4.2 ApprovalStep 모델 (api/app/Models/Tenants/ApprovalStep.php)

4.2.1 상태 상수 추가

const STATUS_ON_HOLD = 'on_hold'; // 추가

4.2.2 fillable 필드 확인

// 추가 대상
'parallel_group', 'acted_by', 'approver_name',
'approver_department', 'approver_position', 'approval_type'

4.2.3 관계 추가

public function actedBy(): BelongsTo
{
    return $this->belongsTo(User::class, 'acted_by');
}

4.3 ApprovalForm 모델 (api/app/Models/Tenants/ApprovalForm.php)

4.3.1 fillable 필드 확인

// 추가 대상
'body_template'

4.4 ApprovalDelegation 모델 신규 생성

파일: api/app/Models/Tenants/ApprovalDelegation.php

MNG의 ApprovalDelegation 모델을 API에 생성한다.

항목 내용
Trait BelongsToTenant, SoftDeletes, Auditable
관계 delegator(), delegate() (BelongsTo User)
스코프 active(), currentlyActive(), forDelegator(userId)
메서드 isEffective() — 현재 유효한 위임인지 확인

4.5 수정 파일 목록 (Phase 1)

파일 작업 유형
api/app/Models/Tenants/Approval.php 상수/fillable/cast/관계/메서드 추가 수정
api/app/Models/Tenants/ApprovalStep.php 상수/fillable/관계 추가 수정
api/app/Models/Tenants/ApprovalForm.php fillable 추가 수정
api/app/Models/Tenants/ApprovalDelegation.php 신규 생성 신규

5. Phase 2 — API 서비스 핵심 워크플로우 이식 완료

5.1 보류 / 보류 해제

MNG 로직 참조: ApprovalService::hold(), releaseHold()

보류 흐름:
1. 현재 결재자만 보류 가능 (pending 상태)
2. 해당 ApprovalStep.status → on_hold
3. Approval.status → on_hold
4. 보류 사유(comment) 기록

보류 해제 흐름:
1. 보류한 결재자만 해제 가능
2. ApprovalStep.status → pending
3. Approval.status → pending

구현 위치: api/app/Services/ApprovalService.php

public function hold(int $id, string $comment): Approval
public function releaseHold(int $id): Approval

5.2 전결 (Pre-Decide)

MNG 로직 참조: ApprovalService::preDecide()

전결 흐름:
1. 현재 결재자가 전결 처리
2. 현재 ApprovalStep → approved (approval_type = 'pre_decided')
3. 이후 모든 pending 단계 → skipped
4. Approval.status → approved
5. completed_at 설정
public function preDecide(int $id, ?string $comment = null): Approval

5.3 복사 재기안 (Copy for Redraft)

MNG 로직 참조: ApprovalService::copyForRedraft()

복사 재기안 흐름:
1. 완료/반려/회수된 문서만 대상
2. 원본 문서의 content, form_id, title 등 복사
3. 새 Approval 생성 (status = draft)
4. parent_doc_id = 원본 문서 ID
5. 결재선(steps)은 복사하지 않음 (새로 설정)
public function copyForRedraft(int $id): Approval

5.4 결재자 스냅샷 저장

MNG 로직 참조: ApprovalService::saveApprovalSteps()

결재 단계 생성 시 결재자의 현재 시점 이름/부서/직급을 스냅샷으로 저장한다.

private function createApprovalSteps(Approval $approval, array $steps): void
{
    foreach ($steps as $index => $step) {
        $user = User::find($step['approver_id']);
        ApprovalStep::create([
            // ... 기존 필드 ...
            'approver_name' => $user?->name,
            'approver_department' => $user?->department?->name,
            'approver_position' => $user?->position_name,
        ]);
    }
}

5.5 반려 후 재상신 이력 관리

MNG 로직 참조: ApprovalService::submit() (재상신 분기)

재상신 흐름 (rejected → pending):
1. 모든 ApprovalStep.status → pending으로 초기화
2. rejection_history JSON에 반려 이력 추가:
   {
     "round": N,
     "approver_name": "홍길동",
     "approver_position": "팀장",
     "comment": "반려 사유",
     "rejected_at": "2026-03-11 14:30:00"
   }
3. resubmit_count += 1
4. Approval.status → pending

기존 submit() 메서드에 재상신 분기를 추가한다.


5.6 뱃지 카운트 API

MNG 로직 참조: ApprovalService::getBadgeCounts()

public function badgeCounts(int $userId): array
{
    return [
        'pending' => /* 현재 내 차례인 결재 대기 건수 */,
        'draft' => /* 내 임시저장 건수 */,
        'reference_unread' => /* 참조 미열람 건수 */,
        'completed_unread' => /* 완료 미확인 건수 (drafter_read_at IS NULL) */,
    ];
}

5.7 완료함 일괄 읽음

public function markCompletedAsRead(int $userId): int
{
    return Approval::where('drafter_id', $userId)
        ->whereIn('status', [self::STATUS_APPROVED, self::STATUS_REJECTED, self::STATUS_CANCELLED])
        ->whereNull('drafter_read_at')
        ->update(['drafter_read_at' => now()]);
}

5.8 회수 시 recall_reason 저장

기존 cancel() 메서드에 recall_reason 파라미터를 추가한다.

public function cancel(int $id, ?string $reason = null): Approval

5.9 수정 파일 목록 (Phase 2)

파일 작업
api/app/Services/ApprovalService.php hold(), releaseHold(), preDecide(), copyForRedraft(), badgeCounts(), markCompletedAsRead() 추가, submit() 재상신 분기 추가, cancel() reason 파라미터 추가, createApprovalSteps() 스냅샷 로직 추가

6. Phase 3 — API 엔드포인트 추가 완료

6.1 신규 엔드포인트 목록

Method URL 서비스 메서드 설명
POST /v1/approvals/{id}/hold hold() 보류
POST /v1/approvals/{id}/release-hold releaseHold() 보류 해제
POST /v1/approvals/{id}/pre-decide preDecide() 전결
POST /v1/approvals/{id}/copy-for-redraft copyForRedraft() 복사 재기안
GET /v1/approvals/badge-counts badgeCounts() 뱃지 카운트
POST /v1/approvals/mark-completed-read markCompletedAsRead() 완료 일괄 읽음
GET /v1/approvals/completed completed() 완료함 목록
GET /v1/approvals/completed/summary completedSummary() 완료함 현황

6.2 기존 엔드포인트 수정

Method URL 변경 내용
POST /v1/approvals/{id}/cancel reason 파라미터 추가
POST /v1/approvals/{id}/submit 재상신(rejected→pending) 분기 처리
GET /v1/approvals/{id} 응답에 line, department, parentDocument 포함

6.3 FormRequest 추가

파일 용도
HoldRequest.php comment 필수 검증
PreDecideRequest.php comment 선택 검증
CancelRequest.php 수정 reason 선택 검증 추가

6.4 수정 파일 목록 (Phase 3)

파일 작업 유형
api/app/Http/Controllers/Api/V1/ApprovalController.php 6개 메서드 추가 수정
api/routes/api/v1/hr.php 8개 라우트 추가 수정
api/app/Http/Requests/V1/Approval/HoldRequest.php 신규 신규
api/app/Http/Requests/V1/Approval/PreDecideRequest.php 신규 신규
api/app/Http/Requests/V1/Approval/CancelRequest.php reason 추가 수정

7. Phase 4 — 위임(Delegation) 시스템 이식 완료

7.1 개요

결재자가 부재 시(출장, 휴가) 대리인에게 결재 권한을 위임하는 시스템이다.

7.2 위임 CRUD API

Method URL 설명
GET /v1/approval-delegations 위임 목록
POST /v1/approval-delegations 위임 생성
GET /v1/approval-delegations/{id} 위임 상세
PATCH /v1/approval-delegations/{id} 위임 수정
DELETE /v1/approval-delegations/{id} 위임 삭제

7.3 위임 적용 로직

결재함(inbox) 조회 시 위임 대상도 함께 조회한다:

inbox 조회 흐름:
1. 내가 직접 결재자인 문서 조회 (기존)
2. + 내가 현재 유효한 위임의 대리인인 경우, 위임자의 결재 대기 문서도 조회
3. 대리 결재 시 acted_by = 대리인 ID, approval_type = 'delegated'

7.4 수정 파일 목록 (Phase 4)

파일 작업 유형
api/app/Http/Controllers/Api/V1/ApprovalDelegationController.php 신규 신규
api/app/Services/ApprovalDelegationService.php 신규 신규
api/app/Http/Requests/V1/ApprovalDelegation/*.php FormRequest 3개 신규
api/routes/api/v1/hr.php 위임 라우트 추가 수정
api/app/Services/ApprovalService.php inbox() 위임 조회 통합 수정

8. Phase 5 — Leave 연동 이식 미착수

8.1 개요

MNG에서는 휴가 신청 결재가 완료되면 자동으로 Leave 레코드를 생성/승인한다.

8.2 연동 흐름

결재 승인 완료 (approval_forms.code = 'leave')
  └─ handleApprovalCompleted()
     ├─ approval.content에서 leave_type, start_date, end_date 추출
     ├─ Leave 레코드 생성 (status = approved)
     └─ approval_id로 연결

결재 반려/회수
  └─ handleApprovalRejected/Cancelled()
     └─ 연결된 Leave 있으면 상태 동기화 (cancelled)

8.3 수정 파일 목록 (Phase 5)

파일 작업
api/app/Services/ApprovalService.php approve(), reject(), cancel() 후크에 Leave 동기화 추가

9. Phase 6 — 테스트 및 검증 미착수

9.1 테스트 시나리오

# 시나리오 검증 항목
T1 기본 결재 흐름 상신→승인→완료, 스냅샷 저장 확인
T2 반려 후 재상신 rejection_history 저장, resubmit_count 증가, 단계 초기화
T3 보류/보류 해제 on_hold 상태 전환, 보류 사유 기록
T4 전결 이후 단계 skipped, 즉시 완료
T5 회수 recall_reason 저장, 미처리 단계 skipped
T6 복사 재기안 새 draft 생성, parent_doc_id 연결
T7 위임 — 생성/조회 CRUD 정상 동작, 기간 검증
T8 위임 — 대결 처리 inbox에 위임 문서 표시, acted_by 기록
T9 뱃지 카운트 4종 카운트 정확성
T10 Leave 연동 휴가 결재 완료→Leave 자동 생성
T11 Document 동기화 기존 syncToLinkedDocument 정상 동작 유지
T12 하위 호환성 기존 API 요청/응답 형식 변경 없음 확인

9.2 데이터 마이그레이션

주의: DB 스키마 변경이 없으므로 데이터 마이그레이션은 불필요하다. 기존 데이터는 새 필드가 null인 상태로 정상 동작한다.


10. 완료된 작업 요약

10.1 API 프로젝트 (/home/aweso/sam/api) — P1~P4 완료

파일 Phase 상태 작업 내용
app/Models/Tenants/Approval.php P1 상수/fillable/cast/관계/메서드 추가
app/Models/Tenants/ApprovalStep.php P1 BelongsToTenant, SoftDeletes, tenant_id 추가
app/Models/Tenants/ApprovalForm.php P1 ModelTrait 추가, fillable 확장
app/Models/Tenants/ApprovalDelegation.php P1 Auditable, ModelTrait 추가
app/Services/ApprovalService.php P2 48개 public 메서드 (tenant_id 스냅샷 포함)
app/Http/Controllers/Api/V1/ApprovalController.php P3 24개 메서드, FormRequest 적용
routes/api/v1/hr.php P3,P4 45개 라우트 등록
app/Http/Requests/Approval/*.php P3 6개 (Approve, Cancel, Hold, PreDecide, DelegationStore, DelegationUpdate)
database/migrations/2026_03_11_* - approval_steps에 tenant_id + deleted_at 추가

10.2 MNG 프로젝트 (/home/aweso/sam/mng) — 최소 수정

파일 작업 상태
app/Models/Approvals/ApprovalStep.php SoftDeletes, tenant_id 추가
app/Services/ApprovalService.php tenant_id 스냅샷 로직 추가

10.3 DB 마이그레이션

마이그레이션 대상 상태
2026_03_11_100001_add_tenant_id_and_soft_deletes_to_approval_steps_table approval_steps 개발/운영 배포 완료

10.4 문서

문서 경로 상태
프론트엔드 API 명세서 frontend/api-specs/approval-api.md 작성 완료

11. 작업 순서 및 의존성

Phase 1: 모델 확장 ✅ 완료
    │
    ├──→ Phase 2: 서비스 워크플로우 ✅ 완료
    │       │
    │       └──→ Phase 3: 엔드포인트 추가 ✅ 완료
    │
    ├──→ Phase 4: 위임 시스템 ✅ 완료
    │
    └──→ Phase 5: Leave 연동 ❌ 미착수 (P2 완료 후 가능)
            │
            └──→ Phase 6: 테스트 ❌ 미착수 (전체 완료 후)

남은 작업: P5 (Leave 연동) → P6 (테스트 및 검증)

11.1 추가 검증 필요 항목

항목 설명 우선순위
병렬 결재 parallel_group 기반 동시 결재 로직이 API에서 동작하는지 검증 중간
위임 inbox 통합 inbox 조회 시 위임 대상 문서가 함께 조회되는지 검증 중간
대결 처리 대리 결재 시 acted_by, approval_type='delegated' 기록 여부 중간

12. 위험 요소 및 대응

위험 영향도 대응
API 기존 엔드포인트 호환성 깨짐 높음 기존 필드 제거 금지, 신규 필드만 추가 (nullable)
MNG와 API 모델 divergence 심화 중간 MNG 모델은 그대로 유지, API만 확장
위임 + 결재함 조회 성능 저하 중간 위임 조회를 LEFT JOIN으로 최적화, 인덱스 확인
반려 후 재상신 시 데이터 무결성 높음 DB 트랜잭션으로 감싸기, rejection_history append-only
전결 시 Leave 연동 누락 중간 preDecide()에도 completion 후크 추가

13. 제외 범위

항목 사유
e-Sign (전자서명) 별도 프로젝트로 관리 (features/esign/)
MNG Blade 뷰 이식 React에서 별도 구현 (프론트엔드 범위)
MNG 서비스 수정 MNG는 독립 동작 유지
React UI 구현 별도 프론트엔드 작업으로 분리
Swagger 문서 엔드포인트 추가 후 별도 작업

관련 문서

문서 경로 설명
프론트엔드 API 명세서 frontend/api-specs/approval-api.md 28개 엔드포인트 전체 명세
Document↔Approval 브릿지 dev/dev_plans/phase4-approval-integration-plan.md 완료된 Phase 4.2 작업
DB 스키마 (HR) system/database/hr.md 인사 관련 테이블 구조
DB 스키마 (문서) system/database/documents.md 문서/전자서명 테이블 구조
API 개발 규칙 dev/standards/api-rules.md Service-First 패턴
options 컬럼 정책 dev/standards/options-column-policy.md JSON 컬럼 규칙

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