# 휴가관리 모듈 개발 계획서

> **작성일**: 2026-02-26
> **상태**: 설계 중

---

## 1. 개요

### 1.1 목적

근태현황에 포함된 휴가/연차 기능을 **독립된 휴가관리 모듈**로 분리한다.
근로기준법 기반 연차 자동 계산, 휴가 신청/승인, 잔여 연차 관리를 체계적으로 지원한다.

### 1.2 핵심 원칙

- 근태현황의 `vacation` 상태는 **결과 기록**으로만 유지 (휴가 승인 완료 시 자동 기록)
- 휴가 신청/승인/잔여일수 관리는 모두 **휴가관리 모듈**에서 수행
- 기존 API 테이블/모델(`leaves`, `leave_balances`, `leave_policies`, `leave_grants`)을 최대한 활용
- MNG에서 관리자 인터페이스(Blade + HTMX) 구현

### 1.3 현재 상태 분석

| 항목 | API (DB/모델) | MNG (UI/서비스) | 비고 |
|------|:------------:|:--------------:|------|
| `leaves` 테이블 | ✅ 마이그레이션 완료 | ❌ 미구현 | 핵심 테이블 |
| `leave_balances` 테이블 | ✅ 마이그레이션 완료 | ⚠️ 단순 모델만 | 연차 잔액 |
| `leave_policies` 테이블 | ✅ 마이그레이션 완료 | ❌ 미구현 | 연차 정책 설정 |
| `leave_grants` 테이블 | ✅ 마이그레이션 완료 | ❌ 미구현 | 연차 부여 이력 |
| `attendance_requests` 테이블 | ✅ 마이그레이션 완료 | ✅ 신청/승인 구현 | 근태현황에 포함 |
| 연차 자동 차감 | — | ⚠️ 단순 구현 | 복원 로직 없음 |

### 1.4 근태현황과의 역할 분리

| 기능 | 근태현황 (유지) | 휴가관리 (신규) |
|------|:--------------:|:--------------:|
| 출퇴근 기록 | ✅ | — |
| 출장/재택/외근 신청·승인 | ✅ | — |
| `vacation` 상태 표시 | ✅ (결과만) | — |
| 연차 부여/발생 규칙 | — | ✅ |
| 휴가 신청·승인 워크플로우 | — | ✅ |
| 잔여 연차 관리 | — | ✅ |
| 연차 촉진 알림 | — | ✅ (Phase 3) |

---

## 2. 기존 DB 스키마

> API에 이미 마이그레이션 완료된 테이블들. MNG에서 모델만 생성하여 활용한다.

### 2.1 `leaves` 테이블

```
┌──────────────────────────────────────────────────────────┐
│ leaves                                                    │
├────────────────┬─────────────┬────────────────────────────┤
│ id             │ bigint PK   │ Auto Increment              │
│ tenant_id      │ bigint FK   │ → tenants.id                │
│ user_id        │ bigint FK   │ → users.id                  │
│ leave_type     │ enum        │ annual, half_am, half_pm,   │
│                │             │ sick, family, maternity,     │
│                │             │ parental                     │
│ start_date     │ date        │ 시작일                       │
│ end_date       │ date        │ 종료일                       │
│ days           │ decimal(3,1)│ 사용일수 (0.5 = 반차)        │
│ reason         │ text        │ 사유                         │
│ status         │ enum        │ pending, approved, rejected, │
│                │             │ cancelled                    │
│ approved_by    │ bigint      │ 승인자 ID                    │
│ approved_at    │ datetime    │ 승인 일시                    │
│ reject_reason  │ text        │ 반려 사유                    │
│ created_by/    │ bigint      │ 감사 필드                    │
│ updated_by/    │             │                              │
│ deleted_by     │             │                              │
│ timestamps     │             │                              │
│ soft_deletes   │             │                              │
└────────────────┴─────────────┴────────────────────────────┘
INDEX: (tenant_id, user_id), status, (start_date, end_date)
```

### 2.2 `leave_balances` 테이블

```
┌──────────────────────────────────────────────────────────┐
│ leave_balances                                            │
├────────────────┬─────────────┬────────────────────────────┤
│ id             │ bigint PK   │                             │
│ tenant_id      │ bigint FK   │                             │
│ user_id        │ bigint FK   │                             │
│ year           │ int         │ 연도                        │
│ total_days     │ decimal(4,1)│ 부여일수 (기본 15)           │
│ used_days      │ decimal(4,1)│ 사용일수                     │
│ remaining_days │ decimal(4,1)│ storedAs(total - used)       │
└────────────────┴─────────────┴────────────────────────────┘
UNIQUE: (tenant_id, user_id, year)
```

### 2.3 `leave_policies` 테이블

```
┌──────────────────────────────────────────────────────────┐
│ leave_policies                                            │
├──────────────────────┬─────────────┬─────────────────────┤
│ tenant_id            │ bigint UNIQUE│ 테넌트당 1개         │
│ standard_type        │ enum         │ fiscal / hire        │
│ fiscal_start_month   │ tinyint      │ 회계연도 시작월       │
│ fiscal_start_day     │ tinyint      │ 회계연도 시작일       │
│ default_annual_leave │ int          │ 기본 연차 (15)       │
│ additional_leave_per_year │ int     │ 근속 가산 (+1)       │
│ max_annual_leave     │ int          │ 최대 연차 (25)       │
│ carry_over_enabled   │ boolean      │ 이월 허용             │
│ carry_over_max_days  │ int          │ 이월 한도             │
│ carry_over_expiry_months │ int      │ 이월 소멸 개월        │
└──────────────────────┴─────────────┴─────────────────────┘
```

### 2.4 `leave_grants` 테이블

```
┌──────────────────────────────────────────────────────────┐
│ leave_grants                                              │
├────────────────┬─────────────┬────────────────────────────┤
│ tenant_id      │ bigint FK   │                             │
│ user_id        │ bigint FK   │                             │
│ grant_type     │ enum        │ annual, monthly, reward,    │
│                │             │ condolence, other           │
│ grant_date     │ date        │ 부여일                      │
│ grant_days     │ decimal(4,1)│ 부여일수                     │
│ reason         │ text        │ 부여 사유                    │
└────────────────┴─────────────┴────────────────────────────┘
INDEX: (tenant_id, user_id), grant_date, grant_type
```

---

## 3. Phase 1: 기본 휴가관리 (핵심)

> 🔴 **필수** — 연차 조회, 휴가 신청/승인, 잔여일수 관리

### 3.1 MNG 모델 생성

| 모델 | 파일 | 대상 테이블 |
|------|------|------------|
| `Leave` | `app/Models/HR/Leave.php` | `leaves` |
| `LeavePolicy` | `app/Models/HR/LeavePolicy.php` | `leave_policies` |
| `LeaveGrant` | `app/Models/HR/LeaveGrant.php` | `leave_grants` |
| `LeaveBalance` | (기존 수정) | `leave_balances` |

### 3.2 LeaveService 생성

**파일**: `app/Services/HR/LeaveService.php`

| 메서드 | 설명 |
|--------|------|
| `getLeaves(array $filters, int $perPage)` | 휴가 목록 조회 (필터: 사원, 유형, 상태, 기간) |
| `storeLeave(array $data)` | 휴가 신청 등록 (잔여일수 검증 포함) |
| `approve(int $id)` | 승인 처리 → `leave_balances` 차감 → `attendances` 자동 기록 |
| `reject(int $id, ?string $reason)` | 반려 처리 |
| `cancel(int $id)` | 취소 처리 → `leave_balances` 복원 → `attendances` 삭제 |
| `getBalance(int $userId, ?int $year)` | 사원별 연차 잔여일수 조회 |
| `getBalanceSummary(?int $year)` | 전체 사원 잔여일수 요약 |
| `calculateDays(string $type, string $startDate, string $endDate)` | 신청 일수 자동 계산 (주말 제외, 반차=0.5) |

### 3.3 LeaveController (API) 생성

**파일**: `app/Http/Controllers/Api/Admin/HR/LeaveController.php`

| Method | Path | 설명 |
|--------|------|------|
| GET | `/admin/hr/leaves` | 휴가 목록 (HTMX/JSON) |
| POST | `/admin/hr/leaves` | 휴가 신청 등록 |
| POST | `/admin/hr/leaves/{id}/approve` | 승인 |
| POST | `/admin/hr/leaves/{id}/reject` | 반려 |
| POST | `/admin/hr/leaves/{id}/cancel` | 취소 |
| GET | `/admin/hr/leaves/balance` | 전체 사원 잔여일수 요약 |
| GET | `/admin/hr/leaves/balance/{userId}` | 개별 사원 잔여일수 |
| GET | `/admin/hr/leaves/export` | 엑셀(CSV) 내보내기 |

### 3.4 MNG 뷰 컨트롤러

**파일**: `app/Http/Controllers/HR/LeaveController.php`

```
GET /hr/leaves → index 페이지 (휴가관리 메인)
```

### 3.5 뷰 구성

**파일**: `resources/views/hr/leaves/index.blade.php`

```
┌───────────────────────────────────────────────────────┐
│  휴가관리                                               │
├───────────┬───────────┬─────────────┬─────────────────┤
│ 휴가신청  │ 잔여연차  │ 사용현황     │ (Phase 2) 설정  │
│ (탭 1)    │ (탭 2)    │ (탭 3)      │ (탭 4)          │
└───────────┴───────────┴─────────────┴─────────────────┘
```

**탭 1: 휴가신청 목록**

```
┌─────────────────────────────────────────────────┐
│ [+ 휴가 신청] [엑셀 내보내기]                     │
│                                                   │
│ 필터: [사원 ▼] [유형 ▼] [상태 ▼] [기간 ~]        │
│                                                   │
│ ┌──────┬──────┬──────┬──────┬─────┬──────┬─────┐ │
│ │ 사원 │ 유형 │ 기간 │ 일수 │사유 │ 상태 │ 처리│ │
│ ├──────┼──────┼──────┼──────┼─────┼──────┼─────┤ │
│ │홍길동│ 연차 │2/24~ │ 1.0 │개인 │ 대기 │승인 │ │
│ │      │      │ 2/24 │     │사유 │      │반려 │ │
│ │김영희│ 반차 │2/25  │ 0.5 │병원 │ 승인 │취소 │ │
│ │      │(오전)│      │     │     │      │     │ │
│ └──────┴──────┴──────┴──────┴─────┴──────┴─────┘ │
│                                                   │
│ [페이지네이션]                                     │
└─────────────────────────────────────────────────┘
```

**탭 2: 잔여연차 현황**

```
┌─────────────────────────────────────────────────┐
│ 연도: [2026 ▼]                                    │
│                                                   │
│ ┌──────┬──────┬──────┬──────┬──────┬──────────┐ │
│ │ 사원 │ 부서 │ 입사일│ 부여 │ 사용 │ 잔여     │ │
│ ├──────┼──────┼──────┼──────┼──────┼──────────┤ │
│ │홍길동│ 개발 │21.03 │ 20.0 │ 5.0 │ 15.0     │ │
│ │김영희│ 영업 │23.08 │ 15.0 │ 3.5 │ 11.5     │ │
│ │이민수│ 총무 │25.06 │ 11.0 │ 2.0 │  9.0     │ │
│ └──────┴──────┴──────┴──────┴──────┴──────────┘ │
└─────────────────────────────────────────────────┘
```

**탭 3: 사용현황 통계**

```
┌─────────────────────────────────────────────────┐
│ 기간: [2026 ▼] [전체/부서별 ▼]                    │
│                                                   │
│ 유형별 집계: 연차 45건 | 반차 12건 | 병가 3건     │
│                                                   │
│ 월별 사용 추이 차트 (선택적)                      │
│                                                   │
│ ┌──────┬──────┬──────┬──────┬──────┬──────────┐ │
│ │ 사원 │ 연차 │ 반차 │ 병가 │ 경조 │ 합계     │ │
│ ├──────┼──────┼──────┼──────┼──────┼──────────┤ │
│ │홍길동│  3.0 │  1.0 │  1.0 │  0.0 │  5.0     │ │
│ │김영희│  2.0 │  1.5 │  0.0 │  0.0 │  3.5     │ │
│ └──────┴──────┴──────┴──────┴──────┴──────────┘ │
└─────────────────────────────────────────────────┘
```

### 3.6 휴가 신청 모달

```
┌──────────────────────────────────────────┐
│ 휴가 신청                                 │
├──────────────────────────────────────────┤
│ 사원:    [홍길동 ▼]     잔여: 15.0일      │
│ 유형:    [연차 ▼]                         │
│ 기간:    [2026-02-27] ~ [2026-02-28]     │
│ 일수:    2.0일 (자동 계산, 주말 제외)      │
│ 사유:    [                          ]     │
│                                           │
│           [취소]  [신청]                   │
└──────────────────────────────────────────┘
```

### 3.7 근태현황 연동 로직

```
휴가 승인 시:
┌──────────┐     ┌──────────────┐     ┌──────────────┐
│ Leave     │──→  │ LeaveBalance │──→  │ Attendance   │
│ approved  │     │ used_days +N │     │ status=      │
│           │     │              │     │ vacation     │
└──────────┘     └──────────────┘     └──────────────┘

휴가 취소 시:
┌──────────┐     ┌──────────────┐     ┌──────────────┐
│ Leave     │──→  │ LeaveBalance │──→  │ Attendance   │
│ cancelled │     │ used_days -N │     │ 해당 날짜    │
│           │     │              │     │ 레코드 삭제  │
└──────────┘     └──────────────┘     └──────────────┘
```

### 3.8 근태현황 정리 작업

Phase 1 구현 후 근태현황에서 다음을 정리:

- `AttendanceRequest`의 `vacation` 유형 → 휴가관리로 이관 (출장/재택/외근만 유지)
- `AttendanceService.deductLeaveBalance()` 제거 → `LeaveService`로 일원화
- `AttendanceController.leaveBalance()` 제거 → `LeaveController`로 이관

---

## 4. Phase 2: 연차 정책 및 자동 계산

> 🟡 **중요** — 근로기준법 기반 연차 자동 발생, 정책 설정

### 4.1 연차 정책 설정 UI

**탭 4: 휴가 설정** (Phase 2에서 활성화)

```
┌──────────────────────────────────────────┐
│ 연차 기준                                 │
│ ○ 입사일 기준  ● 회계연도 기준            │
│ 회계연도 시작: [1]월 [1]일                │
│                                           │
│ 연차 일수                                 │
│ 기본 연차:              [15]일             │
│ 2년 초과 시 가산:       [1]일/2년          │
│ 최대 연차:              [25]일             │
│                                           │
│ 이월 설정                                 │
│ □ 잔여 연차 이월 허용                     │
│   이월 한도: [5]일                        │
│   이월 소멸: [3]개월 후                   │
│                                           │
│               [저장]                       │
└──────────────────────────────────────────┘
```

### 4.2 LeavePolicyService

**파일**: `app/Services/HR/LeavePolicyService.php`

| 메서드 | 설명 |
|--------|------|
| `getPolicy()` | 현재 테넌트 연차 정책 조회 |
| `updatePolicy(array $data)` | 정책 저장/수정 |
| `calculateAnnualLeave(int $userId)` | 사원별 연차 자동 계산 (입사일 + 근속년수 기반) |
| `generateAnnualLeaves()` | 전체 사원 연차 일괄 발생 (연초/입사일 기준) |
| `processCarryOver()` | 이월 처리 (연말) |

### 4.3 연차 발생 규칙 (근로기준법)

```php
// 입사일 기준 연차 계산 로직
function calculateAnnualDays(Carbon $hireDate): float
{
    $years = $hireDate->diffInYears(now());

    if ($years < 1) {
        // 1년 미만: 매월 개근 시 1일 (최대 11일)
        $months = $hireDate->diffInMonths(now());
        return min($months, 11);
    }

    // 1년 이상: 15일 + (근속년수-1)/2 가산 (최대 25일)
    $base = 15;
    $additional = max(0, floor(($years - 1) / 2));
    return min($base + $additional, 25);
}
```

### 4.4 연차 부여 이력 관리

`leave_grants` 테이블을 활용하여 부여 이력 추적:

| grant_type | 설명 | 예시 |
|-----------|------|------|
| `annual` | 연차 자동 발생 | 2026년 연차 15일 부여 |
| `monthly` | 1년 미만 월차 | 2026-03 월차 1일 부여 |
| `reward` | 포상 휴가 | 우수사원 포상 2일 |
| `condolence` | 경조사 | 결혼 경조 5일 |
| `other` | 기타 | 회사 지정 휴가 |

---

## 5. Phase 3: 고급 기능

> 🟢 **권장** — 연차 촉진, 알림, 리포트

### 5.1 연차 촉진제도 (근로기준법 제61조)

| 시기 | 내용 | 자동화 |
|------|------|--------|
| 만료 6개월 전 | 1차 촉진 통보 (미사용 일수 안내) | 카카오 알림톡 발송 |
| 근로자 미응답 시 | 사용 시기 지정 촉구 | 리마인더 알림 |
| 만료 2개월 전 | 2차 촉진 통보 (회사 지정) | 카카오 알림톡 + 이력 보관 |

### 5.2 알림 기능

- 휴가 승인/반려 시 신청자에게 알림톡
- 잔여 연차 N일 이하 시 사용 권고 알림
- 연차 소멸 D-30, D-7 자동 알림

### 5.3 리포트

- 부서별/사원별 연차 사용율 대시보드
- 연차 대장 엑셀 출력 (노무감사 대비)
- 월별 사용 추이 통계

---

## 6. 구현 순서

### Phase 1 (기본 — 약 1~2일)

| Step | 작업 | 파일 |
|------|------|------|
| 1 | MNG 모델 생성 (Leave, LeavePolicy, LeaveGrant) | `app/Models/HR/` |
| 2 | LeaveService 생성 | `app/Services/HR/LeaveService.php` |
| 3 | LeaveController (API) 생성 | `app/Http/Controllers/Api/Admin/HR/` |
| 4 | API 라우트 등록 | `routes/api.php` |
| 5 | 뷰 컨트롤러 생성 | `app/Http/Controllers/HR/LeaveController.php` |
| 6 | 웹 라우트 등록 | `routes/web.php` |
| 7 | index.blade.php 작성 (3개 탭) | `resources/views/hr/leaves/` |
| 8 | 파셜 뷰 작성 (목록, 잔여, 통계) | `resources/views/hr/leaves/partials/` |
| 9 | 근태현황 vacation 연동 정리 | AttendanceService 수정 |
| 10 | 메뉴 등록 안내 | tinker 명령 제공 |

### Phase 2 (정책 — 약 1일)

| Step | 작업 |
|------|------|
| 1 | LeavePolicyService 생성 |
| 2 | 연차 자동 계산 로직 구현 |
| 3 | 설정 UI (탭 4) 추가 |
| 4 | 연차 일괄 발생 Artisan 명령 |
| 5 | 이월 처리 로직 |

### Phase 3 (고급 — 추후)

| Step | 작업 |
|------|------|
| 1 | 연차 촉진 스케줄러 |
| 2 | 카카오 알림톡 연동 |
| 3 | 연차 대장 리포트 |
| 4 | 대시보드 위젯 |

---

## 7. 핵심 설계 결정

| 항목 | 결정 | 이유 |
|------|------|------|
| 신규 테이블 | 불필요 | API에 4개 테이블 이미 존재 |
| 휴가 신청 테이블 | `leaves` 사용 | `attendance_requests`에서 vacation 분리 |
| 반차 처리 | `leave_type`=`half_am`/`half_pm`, `days`=0.5 | 기존 enum 활용 |
| 연차 차감 시점 | 승인 시 즉시 차감 | 잔여일수 실시간 반영 |
| 근태 연동 | 승인 시 Attendance 자동 생성 | 기존 패턴 유지 |
| 취소 시 복원 | used_days 복원 + Attendance 삭제 | 데이터 일관성 |

---

## 관련 문서

- `plans/attendance-management-plan.md` — 근태현황 개발 계획
- `rules/attendance-api.md` — 근태 API 규칙

---

**최종 업데이트**: 2026-02-26
