HBEA410_휴가신청
(Hereby) 직원 앱 > 결재 허브([[HBEA400_결재 허브]]) > 휴가신청
| 앱 | 직원 앱 (Hereby Works, 포트 4002) |
| 대상 사용자 | 조건3 [EMPLOYEE] (self scope 인증 세션) |
| 프로토타입 | TBU |
| 관련 정책 | [[POL_결재워크플로우]] · [[POL_연차부여]] |
| Status | review |
| Owner | 카나 |
개요
휴가신청은 결재 허브([[HBEA400_결재 허브]]) 첫 서브탭으로, 상단 연차현황 카드 + 신청 내역 리스트 + FAB(+) 휴가 신청 폼으로 구성된다. 신청은 leave_requests 레코드를 self scope로 생성(status=pending)하며, 승인/반려/취소는 관리자 결재([[HBAD251_연차 현황·결재]])가 처리한다 — 직원 측에는 상세 진입·취소 경로가 없다. 휴가 유형은 Admin 휴가 유형([[HBAD261_유형 설정 (연장근무·외근·휴가)]])을 소비하며, 커스텀 유형 문자열도 관측된다(census 실데이터 "ㅅㄷㄴㅅ"). 공통 서브탭 골격(상태 필터 칩·FAB·신청→결재 흐름)은 [[HBEA400_결재 허브]]을 참조한다(중복 정의 금지).
문서 히스토리
| 날짜 | 작성자 | 팀 | 변경 내용 |
|---|---|---|---|
| 2026-07-06 | 카나 | PM | 최초 작성 |
| 2026-07-10 | 카나 | PM | 라이브 기능 검토 반영 |
| 2026-07-13 | 카나 | PM | 버튼 행동 명세 보강 |
| 2026-07-21 | 카나 | PM | GC700 깊이 재작성 |
| 2026-07-21 | 카나 | PM | 셀 스타일 확정판 적용 (콸 승인) |
| 2026-07-21 | 카나 | PM | TC 크로스체크 확정분 반영 (🟡) |
| 2026-07-23 | 카나 | PM | 코드 변경 반영(2577546·425fac8·3b25572·318f7e1) |
| 2026-07-24 | 카나 | PM | 코드 변경 반영(04b1b82·7e16a37) |
Data I/O
Server → Client
| 변수 | DB 엔티티.컬럼 | 값 없을 때 | 비고 |
|---|---|---|---|
| $ | leave_grants.grantedDays − 승인 사용일수 | 0/빈값 표기 | census "11 일 남음 / 올해 11일 중 0일 사용" |
| $ | leave_requests[] (self) | Empty "휴가 신청 내역이 없어요" | 본인 신청만 |
| $ | auth.getCurrentUser().leaveReasons (org 커스텀 휴가 사유) | 빈 select | 원천 = 조직 설정 organization.updateSettings({leaveReasons}) (HBAD 조직 설정). picker는 사유 id를 소비 (TC 확정: leave-request-custom-reason.test.ts). 모델 변경: 유형은 절대 시간이 아니라 차감 배수(종일 1 / 반차 0.5 / 반반차 0.25 / 차감 없음 0) + 시간 단위(hourly) 유형 2원 체계 (구 {hours, deductionDays} 서술 대체, 코드 반영: 318f7e1 leaveReasonSchema) |
| $ | leave_requests.status 집계 | 카운트 0은 라벨만 | census "전체 3 / 대기중 2 / 취소 1" |
Client → Server
| 액션 | 전달 값 | tRPC procedure | 권한 토큰 |
|---|---|---|---|
| 휴가 신청 ("신청하기") | leaveType·startDate·endDate·reason (·isHalfDay·halfDayPeriod). 시간 단위 유형 시: startTime·requestedMinutes(30분 단위) 추가 | leaveRequest.request (TC 라이브 확인) — leaveType은 enum 고정이 아니라 org 커스텀 휴가 사유 id를 수용. 커스텀 사유가 설정된 조직에서는 기본 LeaveType enum(ANNUAL 등) 전달 시 "Invalid leave type" throw(legacy enum 거부) (TC 확정: leave-request-custom-reason.test.ts). 서버 가드: 신청 기간 전체가 법정유급휴일(공휴일)이면 거절, 기간에 낀 공휴일은 차감 자동 제외 (코드 반영: 425fac8) / 당일 신청 시 이미 지난 시작 시간 차단 (코드 반영: 3b25572) / 유형별 허용 팀 아니면 본인 소속 팀 신청 차단 (코드 반영: 7e16a37) / 시간 단위 유형 선택 시 유형 defaultMinutes를 사용 시간에 프리필 (코드 반영: 04b1b82) (코드 반영: 2577546 LeaveRequest.requestedMinutes·startTime migration 1855) | self |
| 상태 필터 | statusKey | 리스트 필터 ([[HBEA400_결재 허브]]) | self |
외부 통합 (cross-app)
| 외부 시스템 | 데이터 | 동기/비동기 | 비고 |
|---|---|---|---|
| 관리자 결재 [[HBAD251_연차 현황·결재]] | 대기중 → 승인/반려/취소 상태 write-back | 비동기 | 직원 측 취소 경로 없음 |
| Admin 휴가 유형 [[HBAD261_유형 설정 (연장근무·외근·휴가)]] | leaveType 원천 | 동기 | select 옵션 |
| 연차 부여 기준 [[HBAD262_연차 부여 기준]] | grantedDays 부여 기준 | 동기 | 잔여 산정 |
화면 플로우 (Mermaid 의무)
휴가신청 사양표
| 번호 | 항목/기능명 | 타입 | 사양 및 기능 설명 | 연관/영향 범위 | 비고 |
|---|---|---|---|---|---|
| 00 | 화면 진입 조건 | - | 조건1 [직원 인증] · 결재 허브 "휴가신청" 서브탭 선택 시 노출 | 인증 세션 (self) | |
| 01 | 초기 상태 | Output | 연차현황 카드 + 상태 필터 칩 + 신청 내역 리스트. CASE01 [내역 있음] → 행 목록 / CASE02 [0건] → Empty 문구 | $ | |
| 10 | 연차현황 카드 | Card | 상단 카드. "11 일 남음" 강조 + "올해 11일 중 0일 사용" 보조. CASE01 [부여·사용 있음] → 잔여 계산값 표기 | leave_grants (grantedDays) · 사용일수 집계 | census 실데이터. TBU: 부여 데이터 부재 → 잔여 0/빈값 표기(추정) |
| 20 | 상태 필터 칩 | Filter | • 신청 내역을 상태로 거르는 칩. "전체 3" / "대기중 2" / "승인" / "반려" / "취소 1"(카운트 0은 라벨만). 공통 사양 [[HBEA400_결재 허브]] • 클릭 → 목록 필터 | leave_requests.status | census 실 카운트 |
| 30 | 신청 내역 리스트 | List | 본인 휴가 신청 행 목록. 필터 칩에 종속 | leave_requests[] | TBU: ${leave_requests} 부재 → 리스트 비우고 Empty / 로드 실패 UI 미검증(무반응이면 버그 후보) |
| ㄴ31 | 내역 행 | Row | • 1행 = {유형} / {날짜} / {사유} / {상태 배지}. 상세 진입·직원 취소 경로 없음, 취소는 관리자 결재 [[HBAD251_연차 현황·결재]] 처리 • 클릭 → 무반응(비인터랙티브) | leave_requests (leaveType·startDate·reason·status) | census: "ㅅㄷㄴㅅ 7월 14일 가족 행사dd 대기" 등 |
| ㄴ32 | 상태 배지 | Badge | "대기" / 승인 / 반려 / 취소. CASE01 [대기] · CASE04 [취소] 관측 | leave_requests_status_enum | TBU: enum 매핑 |
| ㄴ30-E | Empty | Text | 신청 0건 → "휴가 신청 내역이 없어요" (원문 카피) | ||
| 40 | 신청 작성 FAB | Button (+) | • 휴가 신청 폼을 여는 우하단 "+"(접근성 이름 "새 신청"). 조건3 [EMPLOYEE] self scope • 클릭 → "휴가 신청" 폼 모달 오픈 | 휴가 신청 폼 | 공통 FAB([[HBEA400_결재 허브]]) |
서브 화면 / 모달 사양표
휴가 신청 폼 (모달) 사양표
| 번호 | 항목/기능명 | 타입 | 사양 및 기능 설명 | 연관/영향 범위 | 비고 |
|---|---|---|---|---|---|
| 00 | 진입 조건 | - | 휴가신청 서브탭 FAB(+) "새 신청" 클릭 시 모달 오픈. 제목 "휴가 신청" | 40 FAB | |
| 01 | 초기 상태 | - | 유형 미선택 / 날짜 미입력 / 사유 빈 textarea. "신청하기" 노출 | ||
| 10 | 유형 | Select | • 휴가 유형(사유)을 고르는 드롭다운. org 커스텀 휴가 사유를 소비 — 옵션 원천 = auth.getCurrentUser().leaveReasons, 제출 시 사유 id 전달• 클릭 → 유형 드롭다운 오픈 - 규칙: 커스텀 사유 설정 조직에서는 기본 LeaveType enum(ANNUAL 등) 전달 시 "Invalid leave type" throw (legacy enum 거부) - 규칙: 유형별 팀 제한 — 본인 소속 팀이 유형의 허용 팀이 아니면 신청 불가(서버 검증, 코드 반영: 7e16a37) - CASE01 [시간 단위(hourly) 유형 선택]: 시간 단위 연차 입력 UI(ㄴ21) 노출 + 유형의 defaultMinutes가 사용 시간에 프리필(30분 단위 조정 가능, 코드 반영: 04b1b82) | auth.getCurrentUser().leaveReasons ← organization.updateSettings({leaveReasons}) (HBAD 조직 설정) | 커스텀 사유 id 소비 (TC 확정: leave-request-custom-reason.test.ts) · 유형 = 차감 배수/시간 단위 2원 체계(코드 반영: 318f7e1) · 팀 제한(7e16a37)·프리필(04b1b82) |
| 20 | 시작일·종료일 | DatePicker | • 휴가 적용 기간을 지정하는 "시작일" / "종료일" 2필드 • 입력 → 기간 설정 - 규칙: 신청 기간 전체가 법정유급휴일(공휴일)이면 접수 거절, 기간에 낀 공휴일은 차감 자동 제외 (서버 가드) | leave_requests.startDate / endDate | 코드 반영: 425fac8 |
| ㄴ21 | 시간 단위 연차 입력 | Group | • 시간 단위(hourly) 유형 선택 시에만 노출되는 입력 그룹 (하루 단위 신청만) - 시작 시간: 사용 시작 시각 입력. 당일 신청 시 이미 지난 시작 시간 차단 (서버 가드, 코드 반영: 3b25572) - 사용 시간: 30분 단위 입력 → leave_requests.requestedMinutes. 유형 선택 시 유형의 defaultMinutes가 프리필(이후 30분 단위 조정 가능, 코드 반영: 04b1b82) — 승인 컴포넌트도 유형 기준 사용 시간으로 초기화 - 종료 시간 미리보기: 시작 시간 + 사용 시간으로 자동 계산 표시 • 규칙: 신청 시간 = 차감량(이후 정책 변경돼도 과차감 금지) / 같은 날 복수 신청 가능(시간대 겹침 불가, 합계 ≤ 1일 소정근로시간) / 같은 날 일 단위 휴가와 병존 불가 — 서버 가드 존재 | leave_requests.startTime / requestedMinutes (migration 1855) | 신설 (코드 반영: 2577546) · defaultMinutes 프리필(04b1b82) · 세부 UI 배치 TBU |
| 30 | 사유 | Textarea | • 휴가 사유 자유 입력. "사유 (선택)", placeholder "휴가 사유" • 입력 → leave_requests.reason 채움 | leave_requests.reason | |
| ㄴ31 | 사유 프리셋 | Button group | • 사유 5종 원문 "개인 사정" / "병원 진료" / "가족 행사" / "건강 회복" / "여행" • 클릭 → textarea에 해당 문구 삽입(자유편집 병행) | leave_requests.reason | census 원문 프리셋 |
| 40 | 신청하기 | Button | • 폼을 제출해 휴가 신청을 생성하는 버튼 • 클릭 → 즉시 제출(컨펌 없음), 성공: leave_requests(status=pending) 생성 + 내역 최상단 "대기" 행 추가 + 상태 칩 카운트 증가 + 모달 닫힘 → 관리자 결재([[HBAD251_연차 현황·결재]]) 인입 - 서버 가드: 기간 전체 공휴일 거절(425fac8) / 당일 지난 시작 시간 차단(3b25572) / 유형별 허용 팀 제한 — 본인 소속 팀 미허용 시 신청 차단(7e16a37) | leaveRequest.request({leaveType, startDate, endDate, reason}) (시간 단위 유형 시 startTime·requestedMinutes 추가) — leaveType = org 커스텀 사유 id (enum 아님). 커스텀 사유 설정 조직에서 legacy enum(ANNUAL 등) 전달 시 "Invalid leave type" throw (TC 확정: 01:1.3 leave / leave-request-custom-reason.test.ts) | 코드 반영: 2577546·425fac8·3b25572·7e16a37(팀 제한)·04b1b82(프리필) · TBU: 제출 실패 UI(무반응이면 버그 후보) / 유형·날짜 미입력 시 제출 차단 여부 |
연동·플로우
- 신청 흐름: STEP1 [신청] FAB("새 신청") → 휴가 신청 폼 "신청하기"(
leaveRequest.request) → leave_requests(status=pending) 생성 → 내역 최상단 "대기" 행 추가·상태 칩 카운트 증가 → STEP2 [결재] 관리자 결재([[HBAD251_연차 현황·결재]]) 승인/반려/취소 → 상태 배지·필터 카운트 갱신. 직원 측 내역 행은 비인터랙티브(상세·취소 없음). - 연차 산정: 부여(leave_grants.grantedDays) − 승인 사용일수 = 잔여. 부여 기준은 Admin 연차 부여 기준([[HBAD262_연차 부여 기준]]) 소관. 모델 변경: 잔여 계산이 불변 원장(LeaveLedgerEntry, append-only) 기반으로 전환됨 (코드 반영: 318f7e1 — 상세 [[HBAD262_연차 부여 기준]]).
- 부수 변경 (코드 반영: 2577546 본문): 외근 신청 사유가 선택 입력으로 전환, 연장·외근·문서 모달 문구 정비 — 해당 모달은 [[HBEA420]] 소관(본 문서 외).
권한 / 상태 요약
| 시나리오 | 트리거 | 결과 | 비고 |
|---|---|---|---|
| CASE01 [폼 제출 성공] | "신청하기" | 대기 행 추가 + 칩 카운트 증가 | self scope |
| CASE02 [승인] | 관리자 결재 | 상태 배지 "승인", 연차 차감 | [[HBAD251_연차 현황·결재]] |
| CASE03 [반려] | 관리자 결재 (사유 필수) | 상태 배지 "반려" | 반려 사유 placeholder "반려사유를 입력해 주세요." |
| CASE04 [취소] | 관리자/TM 경로 | 상태 배지 "취소" | 직원 self-cancel 없음 |
| 조건3 [EMPLOYEE] | 내역 행 클릭 | 무반응 (비인터랙티브) | 상세·취소 경로 없음 |
Open Question
유형 커스텀 문자열("ㅅㄷㄴㅅ")이 Admin 유형 미선택 시 자유입력인지, 테스트 데이터 잔재인지. TBU.→ 해소: leaveType은 org 설정 커스텀 휴가 사유(organization.updateSettings({leaveReasons:[{id, name, hours, deductionDays}]}))의 id를 수용하며, picker는auth.getCurrentUser().leaveReasons를 소비. "ㅅㄷㄴㅅ" 등은 org 커스텀 사유 name (TC 확정: leave-request-custom-reason.test.ts).- 반차(isHalfDay/halfDayPeriod) 폼 노출 여부 및 잔여 차감 단위(0.5일). TBU.
- 필수값 미입력·제출 실패 시 폼 동작(차단/토스트/무반응). 라이브 미검증. TBU.