# 휴가관리 모듈 개발 계획서 > **작성일**: 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