# Phase 3: 비즈니스 핵심 기능

**기간:** 2-3주
**우선순위:** 최고 (실제 비즈니스 가치 창출)
**의존성:** Phase 1 (회원, 거래처), Phase 2 (카테고리)

## 📋 Phase 개요

실제 비즈니스 가치를 창출하는 핵심 기능을 구현합니다.

**포함 기능:**
1. 영업관리 (Sales Management)
2. 견적서관리 (Quotation Management)
3. 전자결재관리 (Approval Management)

---

## 1️⃣ 영업관리 (Sales Management)

### 기능 목록

#### 1.1 영업 기회 목록 조회
- **경로:** `/mng/sales`
- **기능:**
  - 칸반 보드 뷰 (파이프라인 단계별)
  - 리스트 뷰 (테이블 형식)
  - 검색 (거래처명, 담당자, 제목)
  - 필터 (단계, 담당 영업, 예상 매출)
  - 정렬 (생성일, 예상 매출, 마감 예정일)
- **권한:** `sales.index`

#### 1.2 영업 기회 상세 조회
- **경로:** `/mng/sales/{id}`
- **기능:**
  - 기본 정보 (제목, 거래처, 예상 매출)
  - 파이프라인 단계 (Lead → Qualified → Proposal → Negotiation → Won/Lost)
  - 활동 이력 (상담, 미팅, 통화)
  - 관련 견적서
  - 메모/코멘트
- **권한:** `sales.show`

#### 1.3 영업 기회 생성
- **경로:** `/mng/sales/create`
- **기능:**
  - 제목, 거래처 선택
  - 예상 매출, 마감 예정일
  - 초기 단계 설정
  - 담당 영업 할당
- **권한:** `sales.create`

#### 1.4 영업 기회 수정
- **경로:** `/mng/sales/{id}/edit`
- **기능:**
  - 기본 정보 수정
  - 단계 변경 (드래그앤드롭 또는 드롭다운)
  - 담당자 변경
  - Win/Loss 사유 기록
- **권한:** `sales.update`

#### 1.5 활동 이력 추가
- **경로:** `/mng/sales/{id}/activities`
- **기능:**
  - 활동 유형 (통화, 미팅, 이메일, 방문)
  - 활동 내용, 날짜/시간
  - 다음 액션 계획
- **권한:** `sales.activities.create`

### DB 스키마

```sql
CREATE TABLE sales_opportunities (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '소속 테넌트',
    client_id BIGINT UNSIGNED NOT NULL COMMENT '거래처 ID',
    title VARCHAR(255) NOT NULL COMMENT '영업 기회 제목',
    description TEXT NULL COMMENT '상세 설명',
    stage ENUM('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost') DEFAULT 'lead' COMMENT '파이프라인 단계',
    expected_revenue DECIMAL(15,2) DEFAULT 0 COMMENT '예상 매출',
    probability INT DEFAULT 50 COMMENT '성공 확률 (0-100)',
    expected_close_date DATE NULL COMMENT '마감 예정일',
    actual_close_date DATE NULL COMMENT '실제 마감일',
    assigned_user_id BIGINT UNSIGNED NULL COMMENT '담당 영업',
    win_loss_reason TEXT NULL COMMENT 'Win/Loss 사유',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_id (tenant_id),
    INDEX idx_client_id (client_id),
    INDEX idx_stage (stage),
    INDEX idx_assigned_user_id (assigned_user_id),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
    FOREIGN KEY (assigned_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE sales_activities (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    opportunity_id BIGINT UNSIGNED NOT NULL COMMENT '영업 기회 ID',
    user_id BIGINT UNSIGNED NOT NULL COMMENT '활동 수행자',
    activity_type ENUM('call', 'meeting', 'email', 'visit', 'note') DEFAULT 'note' COMMENT '활동 유형',
    subject VARCHAR(255) NOT NULL COMMENT '제목',
    description TEXT NULL COMMENT '내용',
    activity_date DATETIME NOT NULL COMMENT '활동 일시',
    next_action TEXT NULL COMMENT '다음 액션 계획',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_opportunity_id (opportunity_id),
    INDEX idx_user_id (user_id),
    INDEX idx_activity_date (activity_date),
    FOREIGN KEY (opportunity_id) REFERENCES sales_opportunities(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### API 엔드포인트

| Method | Endpoint | Description | FormRequest |
|--------|----------|-------------|-------------|
| GET | `/mng/sales` | 영업 기회 목록 (칸반/리스트) | - |
| GET | `/mng/sales/{id}` | 영업 기회 상세 | - |
| POST | `/mng/sales` | 영업 기회 생성 | `StoreSalesRequest` |
| PUT | `/mng/sales/{id}` | 영업 기회 수정 | `UpdateSalesRequest` |
| DELETE | `/mng/sales/{id}` | 영업 기회 삭제 | - |
| POST | `/mng/sales/{id}/activities` | 활동 이력 추가 | `StoreActivityRequest` |
| PUT | `/mng/sales/{id}/stage` | 단계 변경 | `UpdateStageRequest` |

### Service 클래스

```php
// app/Services/SalesService.php
class SalesService
{
    public function list(array $filters, string $view = 'list'): Collection|LengthAwarePaginator;
    public function find(int $id): SalesOpportunity;
    public function create(array $data): SalesOpportunity;
    public function update(SalesOpportunity $opportunity, array $data): SalesOpportunity;
    public function delete(SalesOpportunity $opportunity): bool;
    public function changeStage(SalesOpportunity $opportunity, string $stage): bool;
    public function addActivity(SalesOpportunity $opportunity, array $activityData): SalesActivity;
    public function getPipelineStats(): array; // 단계별 통계
}
```

### 개발 체크리스트

- [ ] `SalesOpportunity`, `SalesActivity` 모델 작성
- [ ] `SalesService` 클래스 작성
- [ ] FormRequest 작성
- [ ] `SalesController` 작성
- [ ] 칸반 보드 UI (Alpine.js + Drag & Drop)
- [ ] 리스트 뷰 UI (테이블)
- [ ] 파이프라인 통계 차트 (Chart.js)
- [ ] i18n 키 작성
- [ ] 테스트 작성

---

## 2️⃣ 견적서관리 (Quotation Management)

### 기능 목록

#### 2.1 견적서 목록 조회
- **경로:** `/mng/quotations`
- **기능:**
  - 페이지네이션
  - 검색 (견적서 번호, 거래처명, 제목)
  - 필터 (상태, 날짜 범위)
  - 정렬 (생성일, 총액)
- **권한:** `quotations.index`

#### 2.2 견적서 상세 조회
- **경로:** `/mng/quotations/{id}`
- **기능:**
  - 견적서 정보 (번호, 날짜, 유효기간)
  - 거래처 정보
  - 품목 목록 (제품, 수량, 단가, 금액)
  - 총액, 부가세, 합계
  - PDF 미리보기
- **권한:** `quotations.show`

#### 2.3 견적서 생성
- **경로:** `/mng/quotations/create`
- **기능:**
  - 거래처 선택
  - 품목 추가 (API에서 제품 조회)
  - 수량, 단가 입력
  - 할인, 부가세 계산
  - 템플릿 선택
  - 메모/비고
- **권한:** `quotations.create`

#### 2.4 견적서 수정
- **경로:** `/mng/quotations/{id}/edit`
- **기능:**
  - 품목 추가/삭제/수정
  - 할인율 변경
  - 유효기간 변경
- **권한:** `quotations.update`

#### 2.5 견적서 PDF 출력
- **경로:** `/mng/quotations/{id}/pdf`
- **기능:**
  - 템플릿 기반 PDF 생성 (DomPDF 또는 Laravel Snappy)
  - 다운로드 또는 이메일 발송
- **권한:** `quotations.pdf`

#### 2.6 견적서 승인 워크플로우
- **경로:** `/mng/quotations/{id}/approve`
- **기능:**
  - 상태 변경 (Draft → Pending → Approved → Rejected)
  - 승인자 지정
  - 승인/반려 사유
- **권한:** `quotations.approve`

### DB 스키마

```sql
CREATE TABLE quotations (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '소속 테넌트',
    client_id BIGINT UNSIGNED NOT NULL COMMENT '거래처 ID',
    sales_opportunity_id BIGINT UNSIGNED NULL COMMENT '연관 영업 기회',
    quotation_number VARCHAR(50) UNIQUE NOT NULL COMMENT '견적서 번호 (자동 생성)',
    title VARCHAR(255) NOT NULL COMMENT '견적서 제목',
    issue_date DATE NOT NULL COMMENT '발행일',
    valid_until DATE NOT NULL COMMENT '유효기간',
    status ENUM('draft', 'pending', 'approved', 'rejected', 'sent') DEFAULT 'draft' COMMENT '상태',
    subtotal DECIMAL(15,2) DEFAULT 0 COMMENT '소계',
    discount_rate DECIMAL(5,2) DEFAULT 0 COMMENT '할인율 (%)',
    discount_amount DECIMAL(15,2) DEFAULT 0 COMMENT '할인 금액',
    tax_amount DECIMAL(15,2) DEFAULT 0 COMMENT '부가세',
    total_amount DECIMAL(15,2) DEFAULT 0 COMMENT '총액',
    notes TEXT NULL COMMENT '비고',
    template_id BIGINT UNSIGNED NULL COMMENT '템플릿 ID',
    created_by BIGINT UNSIGNED NOT NULL COMMENT '생성자',
    approved_by BIGINT UNSIGNED NULL COMMENT '승인자',
    approved_at TIMESTAMP NULL COMMENT '승인 일시',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_id (tenant_id),
    INDEX idx_client_id (client_id),
    INDEX idx_quotation_number (quotation_number),
    INDEX idx_status (status),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
    FOREIGN KEY (sales_opportunity_id) REFERENCES sales_opportunities(id) ON DELETE SET NULL,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE quotation_items (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    quotation_id BIGINT UNSIGNED NOT NULL COMMENT '견적서 ID',
    product_id VARCHAR(100) NULL COMMENT '제품 ID (API 참조)',
    product_name VARCHAR(255) NOT NULL COMMENT '제품명',
    description TEXT NULL COMMENT '설명',
    quantity DECIMAL(10,2) NOT NULL COMMENT '수량',
    unit_price DECIMAL(15,2) NOT NULL COMMENT '단가',
    discount_rate DECIMAL(5,2) DEFAULT 0 COMMENT '할인율 (%)',
    discount_amount DECIMAL(15,2) DEFAULT 0 COMMENT '할인 금액',
    subtotal DECIMAL(15,2) NOT NULL COMMENT '소계',
    sort_order INT DEFAULT 0 COMMENT '정렬 순서',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_quotation_id (quotation_id),
    FOREIGN KEY (quotation_id) REFERENCES quotations(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### API 엔드포인트

| Method | Endpoint | Description | FormRequest |
|--------|----------|-------------|-------------|
| GET | `/mng/quotations` | 견적서 목록 | - |
| GET | `/mng/quotations/{id}` | 견적서 상세 | - |
| POST | `/mng/quotations` | 견적서 생성 | `StoreQuotationRequest` |
| PUT | `/mng/quotations/{id}` | 견적서 수정 | `UpdateQuotationRequest` |
| DELETE | `/mng/quotations/{id}` | 견적서 삭제 | - |
| GET | `/mng/quotations/{id}/pdf` | PDF 출력 | - |
| POST | `/mng/quotations/{id}/approve` | 승인/반려 | `ApproveQuotationRequest` |
| POST | `/mng/quotations/{id}/send` | 이메일 발송 | `SendQuotationRequest` |

### Service 클래스

```php
// app/Services/QuotationService.php
class QuotationService
{
    public function list(array $filters): LengthAwarePaginator;
    public function find(int $id): Quotation;
    public function create(array $data): Quotation;
    public function update(Quotation $quotation, array $data): Quotation;
    public function delete(Quotation $quotation): bool;
    public function generatePDF(Quotation $quotation): string; // PDF 경로
    public function approve(Quotation $quotation, int $approverId): bool;
    public function reject(Quotation $quotation, string $reason): bool;
    public function send(Quotation $quotation, string $email): bool;
    public function calculateTotals(array $items): array; // 금액 계산
    public function generateQuotationNumber(): string; // QT-20251121-0001
}
```

### 개발 체크리스트

- [ ] `Quotation`, `QuotationItem` 모델 작성
- [ ] `QuotationService` 클래스 작성
- [ ] FormRequest 작성
- [ ] `QuotationController` 작성
- [ ] 품목 추가 UI (Alpine.js 동적 행 추가)
- [ ] PDF 생성 기능 (DomPDF)
- [ ] 템플릿 시스템 연동
- [ ] 견적서 번호 자동 생성 로직
- [ ] API 서버 제품 조회 연동
- [ ] i18n 키 작성
- [ ] 테스트 작성

---

## 3️⃣ 전자결재관리 (Approval Management)

### 기능 목록

#### 3.1 결재 문서 목록 조회
- **경로:** `/mng/approvals`
- **기능:**
  - 내 결재 대기 문서
  - 내가 요청한 문서
  - 완료된 문서
  - 검색 (제목, 요청자)
  - 필터 (상태, 문서 유형)
- **권한:** `approvals.index`

#### 3.2 결재 문서 상세 조회
- **경로:** `/mng/approvals/{id}`
- **기능:**
  - 문서 정보 (제목, 내용, 첨부파일)
  - 결재선 (요청자 → 결재자1 → 결재자2 → ...)
  - 결재 이력 (승인/반려 사유, 일시)
  - 현재 결재 단계
- **권한:** `approvals.show`

#### 3.3 결재 요청
- **경로:** `/mng/approvals/create`
- **기능:**
  - 문서 유형 선택 (휴가, 지출, 구매 등)
  - 템플릿 불러오기
  - 내용 작성
  - 결재선 설정 (순차/병렬)
  - 첨부파일 업로드
- **권한:** `approvals.create`

#### 3.4 결재 승인/반려
- **경로:** `/mng/approvals/{id}/approve` 또는 `/reject`
- **기능:**
  - 승인/반려 선택
  - 코멘트 작성
  - 다음 결재자에게 알림
- **권한:** `approvals.approve`

#### 3.5 결재선 설정
- **경로:** `/mng/approvals/approval-lines`
- **기능:**
  - 결재선 템플릿 관리
  - 부서별 기본 결재선
  - 순차/병렬 결재 설정
- **권한:** `approvals.lines.manage`

### DB 스키마

```sql
CREATE TABLE approvals (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '소속 테넌트',
    document_type VARCHAR(100) NOT NULL COMMENT '문서 유형 (leave, expense, purchase 등)',
    title VARCHAR(255) NOT NULL COMMENT '제목',
    content TEXT NOT NULL COMMENT '내용',
    requester_id BIGINT UNSIGNED NOT NULL COMMENT '요청자',
    status ENUM('pending', 'in_progress', 'approved', 'rejected', 'cancelled') DEFAULT 'pending' COMMENT '상태',
    current_step INT DEFAULT 1 COMMENT '현재 결재 단계',
    total_steps INT NOT NULL COMMENT '전체 결재 단계',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_id (tenant_id),
    INDEX idx_requester_id (requester_id),
    INDEX idx_status (status),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (requester_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE approval_steps (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    approval_id BIGINT UNSIGNED NOT NULL COMMENT '결재 문서 ID',
    step_order INT NOT NULL COMMENT '결재 순서',
    approver_id BIGINT UNSIGNED NOT NULL COMMENT '결재자',
    status ENUM('waiting', 'approved', 'rejected', 'skipped') DEFAULT 'waiting' COMMENT '상태',
    comment TEXT NULL COMMENT '결재 코멘트',
    approved_at TIMESTAMP NULL COMMENT '결재 일시',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_approval_id (approval_id),
    INDEX idx_approver_id (approver_id),
    INDEX idx_status (status),
    FOREIGN KEY (approval_id) REFERENCES approvals(id) ON DELETE CASCADE,
    FOREIGN KEY (approver_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### API 엔드포인트

| Method | Endpoint | Description | FormRequest |
|--------|----------|-------------|-------------|
| GET | `/mng/approvals` | 결재 문서 목록 | - |
| GET | `/mng/approvals/{id}` | 결재 문서 상세 | - |
| POST | `/mng/approvals` | 결재 요청 | `StoreApprovalRequest` |
| PUT | `/mng/approvals/{id}` | 결재 문서 수정 (대기 상태만) | `UpdateApprovalRequest` |
| POST | `/mng/approvals/{id}/approve` | 승인 | `ApproveRequest` |
| POST | `/mng/approvals/{id}/reject` | 반려 | `RejectRequest` |
| DELETE | `/mng/approvals/{id}` | 결재 취소 | - |

### Service 클래스

```php
// app/Services/ApprovalService.php
class ApprovalService
{
    public function list(int $userId, array $filters): LengthAwarePaginator;
    public function find(int $id): Approval;
    public function create(array $data, array $approvers): Approval;
    public function update(Approval $approval, array $data): Approval;
    public function approve(Approval $approval, int $approverId, string $comment = null): bool;
    public function reject(Approval $approval, int $approverId, string $reason): bool;
    public function cancel(Approval $approval): bool;
    public function getMyPendingApprovals(int $userId): Collection;
    public function notifyNextApprover(Approval $approval): void;
}
```

### 개발 체크리스트

- [ ] `Approval`, `ApprovalStep` 모델 작성
- [ ] `ApprovalService` 클래스 작성
- [ ] FormRequest 작성
- [ ] `ApprovalController` 작성
- [ ] 결재선 UI (순차 흐름 시각화)
- [ ] 알림 시스템 연동 (이메일, 실시간 알림)
- [ ] 문서 유형별 템플릿 연동
- [ ] i18n 키 작성
- [ ] 테스트 작성

---

## 🎯 Phase 3 완료 조건

### 기능 완성도
- [ ] 3개 모듈 모두 CRUD 완성
- [ ] 영업 기회 칸반 보드 동작
- [ ] 견적서 PDF 생성 동작
- [ ] 전자결재 승인 워크플로우 동작

### 코드 품질
- [ ] Service-First 패턴 준수
- [ ] FormRequest 검증 구현
- [ ] BelongsToTenant trait 적용
- [ ] i18n 키 사용
- [ ] Pint, PHPStan 통과

### 비즈니스 로직
- [ ] 영업 파이프라인 단계 전환 정상
- [ ] 견적서 금액 계산 정확
- [ ] 결재선 순차 승인 정상
- [ ] 알림 발송 동작

### 테스트
- [ ] Service 계층 테스트
- [ ] 워크플로우 통합 테스트
- [ ] PDF 생성 테스트
- [ ] 권한 체크 테스트

---

**최종 업데이트:** 2025-11-21
**작성자:** Claude Code
**버전:** 1.0.0