491 lines
18 KiB
Markdown
491 lines
18 KiB
Markdown
|
|
# 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
|