# 휴가 데이터 흐름 가이드 > 작성일: 2026-02-05 > 목적: 휴가 관련 데이터 저장 구조 및 연동 방식 문서화 --- ## 1. 테이블 구조 ### 1.1 vacation_balance_details (연간 휴가 잔액) **목적**: 작업자별 연간 휴가 발생/사용 일수 관리 | 컬럼 | 설명 | 예시 | |------|------|------| | `worker_id` | 작업자 ID | 1 | | `vacation_type_id` | 휴가 **유형** ID (vacation_types 참조) | 1 (이월), 2 (정기연차) | | `year` | 연도 | 2026 | | `total_days` | 발생 일수 | 15 | | `used_days` | 사용 일수 (자동 연동) | 3 | | `remaining_days` | 잔여 일수 (자동 계산 컬럼) | 12 | ### 1.2 daily_attendance_records (일별 근태 기록) **목적**: 일별 출근/휴가/근무시간 기록 | 컬럼 | 설명 | 예시 | |------|------|------| | `worker_id` | 작업자 ID | 1 | | `record_date` | 기록 날짜 | 2026-02-05 | | `attendance_type_id` | 근태 유형 (1=정상, 2=지각, 3=조퇴, 4=결근, 5=휴가) | 5 | | `vacation_type_id` | 휴가 **사용** 유형 (1=연차, 2=반차, 3=반반차) | 1 | | `total_work_hours` | 총 근무시간 | 8.0 | ### 1.3 vacation_types (휴가 유형 마스터) **목적**: 휴가 유형 정의 및 차감 우선순위 | type_code | type_name | priority | 용도 | |-----------|-----------|----------|------| | CARRYOVER | 이월 | 1 | 작년 이월 연차 | | ANNUAL | 정기연차 | 2 | 올해 발생 연차 | | LONG_SERVICE | 장기근속 | 3 | 장기근속 보상 | | WEDDING | 결혼 | 4 | 경조사 | | SPOUSE_BIRTH | 배우자 출산 | 4 | 경조사 | | ... | ... | 4 | 기타 경조사 | --- ## 2. 데이터 흐름 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 휴가 데이터 흐름도 │ └─────────────────────────────────────────────────────────────────┘ [annual-overview.html] [work-status.html] 연간 연차 현황 페이지 근무 현황 페이지 │ │ │ 발생 일수 입력 │ 휴가 사용 기록 │ (이월, 정기연차, 장기근속, 경조사) │ (연차/반차/반반차) ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ vacation_balance │◀───── 연동 ───────│ daily_attendance │ │ _details │ │ _records │ │ │ │ │ │ total_days: 15 │ used_days │ vacation_type_id │ │ used_days: 3 ◀──┼──── 자동 업데이트 ──┼── 1 (연차) │ │ remaining: 12 │ │ │ └──────────────────┘ └──────────────────┘ ``` --- ## 3. 연동 로직 ### 3.1 휴가 사용 시 (work-status.html에서 저장) **파일**: `api.hyungi.net/services/attendanceService.js` ```javascript // upsertAttendanceRecordService 함수 내부 // 1. 기존 기록 조회 const existingRecord = await AttendanceModel.getDailyAttendanceRecords(record_date, worker_id); // 2. 휴가 사용 유형 → 차감 일수 변환 // vacation_type_id: 1=연차(1일), 2=반차(0.5일), 3=반반차(0.25일) const daysMap = { 1: 1, 2: 0.5, 3: 0.25 }; const newDays = daysMap[vacation_type_id] || 0; // 3. 이전 휴가가 있었으면 복구 (변경된 경우) if (previousDays > 0) { await vacationBalanceModel.restoreByPriority(worker_id, year, previousDays); } // 4. 새 휴가 차감 (우선순위: 이월 → 정기연차 → 장기근속 → 경조사) if (newDays > 0) { await vacationBalanceModel.deductByPriority(worker_id, year, newDays); } ``` ### 3.2 차감 우선순위 로직 **파일**: `api.hyungi.net/models/vacationBalanceModel.js` ```javascript // deductByPriority 함수 // 1. 우선순위순으로 잔여 일수가 있는 잔액 조회 SELECT * FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.worker_id = ? AND vbd.year = ? AND (vbd.total_days - vbd.used_days) > 0 ORDER BY vt.priority ASC -- 이월(1) → 정기연차(2) → 장기근속(3) → 경조사(4) // 2. 순서대로 차감 for (const balance of balances) { const available = balance.remaining_days; const toDeduct = Math.min(remaining, available); UPDATE vacation_balance_details SET used_days = used_days + ? WHERE id = ? } ``` --- ## 4. 주요 파일 위치 ``` api.hyungi.net/ ├── models/ │ ├── vacationBalanceModel.js # 휴가 잔액 DB 쿼리 │ │ ├── deductByPriority() # 우선순위 차감 ★ │ │ └── restoreByPriority() # 우선순위 복구 ★ │ └── attendanceModel.js # 근태 기록 DB 쿼리 │ ├── services/ │ └── attendanceService.js # 근태 저장 비즈니스 로직 │ └── upsertAttendanceRecordService() # 연동 로직 포함 ★ │ ├── controllers/ │ ├── vacationBalanceController.js # 휴가 잔액 API │ └── attendanceController.js # 근태 API │ └── routes/ ├── vacationBalanceRoutes.js └── attendanceRoutes.js web-ui/pages/attendance/ ├── annual-overview.html # 연간 연차 현황 (발생 일수 입력) - 관리자 전용 ├── my-vacation-info.html # 내 연차 정보 (잔여 확인 + 연장근로) - 전체 사용자 ★ ├── work-status.html # 근무 현황 (휴가 사용 기록) └── monthly.html # 월별 출근부 (조회 전용) ``` --- ## 5. API 엔드포인트 ### 5.1 휴가 잔액 관련 | Method | Endpoint | 설명 | |--------|----------|------| | GET | `/vacation-balances/year/:year` | 연도별 전체 잔액 조회 | | GET | `/vacation-balances/worker/:workerId/year/:year` | 작업자별 잔액 조회 | | POST | `/vacation-balances/bulk-upsert` | 일괄 저장 (연간 연차 현황) | ### 5.2 근태 기록 관련 | Method | Endpoint | 설명 | |--------|----------|------| | POST | `/attendance/records` | 근태 기록 저장 (휴가 연동 포함) | | GET | `/attendance/records?date=&worker_id=` | 근태 기록 조회 | --- ## 6. 차감 일수 매핑 ### 6.1 휴가 사용 유형 (daily_attendance_records.vacation_type_id) | ID | 유형 | 차감 일수 | |----|------|----------| | 1 | 연차 (ANNUAL_FULL) | 1일 | | 2 | 반차 (ANNUAL_HALF) | 0.5일 | | 3 | 반반차 (ANNUAL_QUARTER) | 0.25일 | ### 6.2 근태 유형 (daily_attendance_records.attendance_type_id) | ID | 유형 | 설명 | |----|------|------| | 1 | NORMAL | 정상 출근 | | 2 | LATE | 지각 | | 3 | EARLY_LEAVE | 조퇴 | | 4 | ABSENT | 결근 | | 5 | VACATION | 휴가 | --- ## 7. 주의사항 ### 7.1 휴가 잔액이 없는 경우 - 잔액이 없어도 근태 기록은 저장됨 - 콘솔에 경고 로그 출력 - 관리자가 연간 연차 현황에서 발생 일수를 먼저 입력해야 함 ### 7.2 휴가 수정 시 - 기존 휴가 → 복구 (restoreByPriority) - 새 휴가 → 차감 (deductByPriority) - 연차 → 반차 변경 시: 1일 복구 → 0.5일 차감 ### 7.3 복구 우선순위 - 차감 우선순위의 역순으로 복구 - 마지막에 차감된 유형부터 먼저 복구 - 예: 경조사(4) → 장기근속(3) → 정기연차(2) → 이월(1) --- ## 8. 테스트 시나리오 ### 8.1 기본 플로우 1. [연간 연차 현황] 김철수에게 정기연차 15일 입력 → 저장 2. [근무 현황] 2026-02-05에 김철수 연차 선택 → 저장 3. [연간 연차 현황] 새로고침 → 총 사용: 1일, 잔여: 14일 확인 ### 8.2 수정 플로우 1. [근무 현황] 기존 연차(1일) → 반차(0.5일)로 변경 → 저장 2. [연간 연차 현황] 새로고침 → 총 사용: 0.5일, 잔여: 14.5일 확인 ### 8.3 우선순위 테스트 1. [연간 연차 현황] 이월 2일, 정기연차 15일 입력 2. [근무 현황] 연차 3회 사용 (3일) 3. [연간 연차 현황] 확인: - 이월: 2일 발생, 2일 사용, 0일 잔여 - 정기연차: 15일 발생, 1일 사용, 14일 잔여 --- ## 9. 내 연차 정보 페이지 (my-vacation-info.html) ### 9.1 기능 - **연차 잔여 현황**: 유형별(이월, 정기연차, 장기근속, 경조사) 잔여 일수 표시 - **월간 연장근로**: 선택한 월의 연장근로 시간 및 일별 상세 ### 9.2 접근 권한 | 사용자 유형 | 기능 | |------------|------| | 일반 사용자 | 본인 정보만 조회 (user.worker_id 기준) | | 관리자 | 작업자 선택하여 조회 가능 | ### 9.3 연장근로 계산 로직 ```javascript // daily_attendance_records에서 8시간 초과분 합산 records.forEach(r => { const hours = parseFloat(r.total_work_hours) || 0; if (hours > 8) { totalOvertimeHours += (hours - 8); } }); ``` ### 9.4 메뉴 위치 - 근태 관리 > 내 연차 정보 (최상단) --- ## 10. 향후 개선 사항 - [ ] 휴가 신청/승인 시스템 연동 (vacation_requests 테이블) - [ ] 연차 촉진제 알림 기능 - [ ] 휴가 사용 내역 상세 조회 기능 - [ ] 부서별 휴가 현황 대시보드