Files
sam-docs/dev/dev_plans/approval-system-unification-plan.md
김보곤 ab0178517e docs: [approval] 결재관리 시스템 통합 계획서 작성
- MNG→API 결재 기능 이식 6단계 계획
- 모델/서비스/엔드포인트/위임/Leave연동 범위 정리
- INDEX.md에 결재관리 필수 문서 등록
2026-03-11 16:46:54 +09:00

22 KiB

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

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


1. 개요

1.1 배경

현재 SAM의 결재관리 시스템이 MNG와 API에 이중 구현되어 있다.

항목 MNG (관리자 패널) API (REST API)
결재 흐름 상신/승인/반려/회수/보류/전결/복사재기안 상신/승인/반려/회수
위임 시스템 완전 구현 미구현
병렬 결재 parallel_group 미구현
결재자 스냅샷 이름/부서/직급 미구현
대결 처리 acted_by 미구현
반려 이력 rejection_history 미구현
뱃지 카운트 4종 뱃지 미구현
양식별 뷰 27개 Blade 파일 (React 측 담당)
Leave 연동 결재 완료→휴가 자동 생성 미구현
Document 동기화 syncToLinkedDocument()
현황 요약 API draftsSummary(), inboxSummary()

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. 현재 상태 분석

2.1 DB 테이블 현황

모든 테이블은 API 마이그레이션으로 이미 생성되어 있다. MNG 모델이 사용하는 컬럼은 DB에 존재하지만, 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 parallel_group
acted_by
approver_name
approver_department
approver_position
approval_type
approval_forms body_template
approval_delegations 전체

핵심: DB 스키마 변경이 거의 불필요하다. API 모델과 서비스만 확장하면 된다.

2.2 API 모델 vs MNG 모델 상태 비교

API 모델 (현재) MNG 모델 (목표) 차이
STATUS_CANCELLED 까지 STATUS_ON_HOLD 추가 상태 1개 추가
isEditable(), isActionable(), isCancellable(), isDeletable() + isHoldable(), isHoldReleasable(), isCopyable(), isDeletableBy(user) 메서드 4개 추가
steps(), approverSteps(), referenceSteps() + parentDocument(), childDocuments() 관계 2개 추가
getStatusColorAttribute() Accessor 1개 추가

2.3 API 서비스 vs MNG 서비스 기능 비교

기능 API MNG 이식 필요
기안함/결재함/참조함 조회
문서 CRUD
상신/승인/반려/회수
보류 (hold)
보류 해제 (releaseHold)
전결 (preDecide)
복사 재기안 (copyForRedraft)
뱃지 카운트 (badgeCounts)
완료함 일괄 읽음
결재자 스냅샷 저장
반려 후 재상신 이력
Leave 연동
현황 요약 (summary) — (유지)
참조 미열람 (markUnread) — (유지)
Document 동기화 — (유지)

3. 작업 범위 및 단계

3.1 전체 단계 요약

Phase 작업 예상 규모 영향 범위
P1 API 모델 확장 중간 API 모델 4개 수정
P2 API 서비스 — 핵심 워크플로우 이식 대규모 ApprovalService 확장
P3 API 엔드포인트 추가 중간 컨트롤러/라우트/FormRequest
P4 위임(Delegation) 시스템 이식 중간 모델+서비스+컨트롤러 신규
P5 Leave 연동 이식 소규모 서비스 로직 추가
P6 테스트 및 검증 중간 전체

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)

파일 Phase 유형 작업 내용
app/Models/Tenants/Approval.php P1 수정 상수/fillable/cast/관계/메서드 추가
app/Models/Tenants/ApprovalStep.php P1 수정 상수/fillable/관계 추가
app/Models/Tenants/ApprovalForm.php P1 수정 fillable 추가
app/Models/Tenants/ApprovalDelegation.php P1 신규 위임 모델
app/Services/ApprovalService.php P2 수정 6개 메서드 추가 + 기존 메서드 확장
app/Services/ApprovalDelegationService.php P4 신규 위임 서비스
app/Http/Controllers/Api/V1/ApprovalController.php P3 수정 6개 액션 메서드 추가
app/Http/Controllers/Api/V1/ApprovalDelegationController.php P4 신규 위임 컨트롤러
routes/api/v1/hr.php P3,P4 수정 13개 라우트 추가
app/Http/Requests/V1/Approval/HoldRequest.php P3 신규
app/Http/Requests/V1/Approval/PreDecideRequest.php P3 신규
app/Http/Requests/V1/Approval/CancelRequest.php P3 수정
app/Http/Requests/V1/ApprovalDelegation/*.php P4 신규 3개

10.2 MNG 프로젝트 (/home/aweso/sam/mng)

MNG는 수정하지 않는다. 기존 MNG 결재 시스템은 자체 서비스/컨트롤러로 독립 동작하며, 이번 작업의 대상이 아니다.

10.3 DB 마이그레이션

마이그레이션 불필요. 모든 필요 컬럼이 이미 API 마이그레이션으로 생성되어 있다. approval_delegations 테이블도 이미 존재한다.


11. 작업 순서 및 의존성

Phase 1: 모델 확장 (독립, 선행 필수)
    │
    ├──→ Phase 2: 서비스 워크플로우 (P1 완료 후)
    │       │
    │       └──→ Phase 3: 엔드포인트 추가 (P2 완료 후)
    │
    ├──→ Phase 4: 위임 시스템 (P1 완료 후, P2와 병렬 가능)
    │       │
    │       └──→ Phase 2에 inbox 위임 통합 (P4 완료 후)
    │
    └──→ Phase 5: Leave 연동 (P2 완료 후)
            │
            └──→ Phase 6: 테스트 (전체 완료 후)

권장 실행 순서: P1 → P2 → P3 → P4 → P5 → P6


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 문서 엔드포인트 추가 후 별도 작업

관련 문서

문서 경로 설명
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