연차 사용촉진
이 문서는 구현된 코드(2026-08-13 기준)를 그대로 기술한 정책 문서예요. 근거는
path:line으로 표기했고, CODE 가 진실이에요. 상위 연차 설정 화면은 연차 관리 를 참고해 주세요.
- 대상 사용자 : 인사 관리자(연차 결재 권한 보유자)
- 목적 : 근로기준법 제61조 연차 사용촉진 — 미사용 연차에 대해 회사가 사용을 촉진하고, 미사용분에 대한 보상 의무를 면제받기 위한 절차를 시스템으로 처리
- 구현 위치
- 라우터 :
packages/api/src/routers/annual-leave-promotion.router.ts(프로시저 15개) - 서비스 :
packages/api/src/services/annual-leave-promotion.service.ts - 상태 enum :
packages/core/src/enums/annual-leave-promotion.enum.ts - 엔티티 :
AnnualLeavePromotion*군 (촉진 본체·서명 시도·이벤트 로그·계획 항목·대상 스냅샷·배치·배치 항목)
- 라우터 :
- 권한 :
Resource.LEAVE— 조회는READ, 발송·지정 등 변경은UPDATE
전체 흐름
- 대상 선정 — 재직(ACTIVE) + 잔여 연차 > 0 인 직원 (
service.ts:1018,1046—getAnnualLeaveBalanceWithLedger로 잔여 확인) - 1차 안내 발송 — hereby Sign 연차 사용계획서 서식으로 계약을 생성·발송하고, Works 메신저에 「서명하기」 액션 메시지를 발행 (
sendFirstNotice,service.ts:471) - (선택) 재안내 — 기한 내 미완료 시 리마인더 메시지 발송, 기한은 변경되지 않음 (
sendReminder,service.ts:1560) - 직원 서명·계획서 제출 — 직원이 서명 세션에서 계획서를 작성·서명 → 완료 동기화 시
applied로 전이하고 계획 항목 저장 (reconcilePromotionCompletion,service.ts:1666) - 기한 초과 자동 취소 — 기한(기본 10일) 내 미서명이면 크론이 Sign 계약을 취소하고
expired_cancelled로 전이 (expireDuePromotions,service.ts:1914) - 2차 시기 지정 —
expired_cancelled건에 대해 회사가 연차 사용시기를 직접 지정하고 안내 메시지 발행 →second_designated(designateSecondPlan,service.ts:1987)
응답 기한 기본값은 10일(
DEFAULT_RESPONSE_DURATION_SECONDS = 10 * 24 * 60 * 60,service.ts:58)이며 1~365일 범위에서 지정 가능해요(service.ts:483).
상태 (business status)
AnnualLeavePromotionBusinessStatus (annual-leave-promotion.enum.ts:2-8)
| 값 | 의미 |
|---|---|
not_notified | 촉진 레코드 생성됨, 1차 안내 발행 전(처리 중) |
awaiting_signature | 1차 안내 발행 완료, 직원 서명 대기 |
applied | 직원이 서명·계획서 제출 완료 |
expired_cancelled | 기한 초과로 Sign 요청 자동 취소 |
second_designated | 회사가 2차로 사용시기 지정 완료 |
- 처리 상태(
AnnualLeavePromotionProcessingStatus:idle·processing·retryable_failure·review_required·completed)는 발송/재시도 같은 기술적 처리 단계를 비즈니스 상태와 분리해 관리해요(enum.ts:11-17). - 서명 시도(
AnnualLeavePromotionAttemptStatus:preparing·pending_publication·awaiting_signature·completed·expired·cancelled·failed·review_required)는 개별 Sign 시도의 생명주기예요(enum.ts:19-28). 안내 1건은 여러 시도(attempt)를 가질 수 있어요.
프로시저 (15개)
| 프로시저 | 권한 | 설명 |
|---|---|---|
list | LEAVE·READ | 촉진 목록 조회 + canRetryFirstNotice·canSecondDesignate 파생 플래그 (router.ts:50) |
templates | LEAVE·UPDATE | 사용 가능한 Sign 서식 목록(p1Compatible 만) (router.ts:98) |
firstNotice | LEAVE·UPDATE | 개별 직원 1차 안내 발송 (router.ts:103) |
retryFirstNotice | LEAVE·UPDATE | 발송 실패(retryable_failure)한 1차 안내 재시도 (router.ts:113) |
reissue | LEAVE·UPDATE | 기한 초과 취소(expired_cancelled)건을 다시 발송 (router.ts:126) |
batchPreflight | LEAVE·UPDATE | 발송 대상 선정 → 스냅샷 저장, selectionToken 반환(15분 유효) (router.ts:144) |
batchCreate | LEAVE·UPDATE | 스냅샷 토큰으로 대량 발송 배치 생성(멱등 operationKey) (router.ts:154) |
batchCancel | LEAVE·UPDATE | 진행 중 배치 취소 요청 (router.ts:163) |
batchStatus | LEAVE·READ | 단일 배치 상태 조회 (router.ts:169) |
batchList | LEAVE·READ | 배치 목록 조회 (router.ts:173) |
batchRetryFailed | LEAVE·UPDATE | 배치 내 실패 항목 재시도 (router.ts:177) |
openSigner | LEAVE·READ | 서명 세션(URL) 발급 (router.ts:183) |
reminder | LEAVE·UPDATE | 서명 대기 건 재안내(기한 불변) (router.ts:196) |
reconcile | LEAVE·UPDATE | Sign 완료 상태를 강제 동기화 (router.ts:209) |
designateSecond | LEAVE·UPDATE | 기한 초과 취소건에 2차 사용시기 지정 (router.ts:215) |
대량 발송(배치)
- 선정 스냅샷:
batchPreflight가 재직·잔여>0 대상을 계산해AnnualLeavePromotionSelectionSnapshot에 저장하고selectionToken(15분 유효)을 반환해요 (service.ts:1008). 선정 모드는EXPLICIT(명시 선택) /ALL_ELIGIBLE_IN_SCOPE(스코프 내 전체 대상) 2종(enum.ts:56-59). - 배치 생성:
batchCreate는operationKey로 멱등 처리하고 스냅샷을 소비해 배치를 만들어요 (service.ts:1065). - 배치 상태:
materializing → queued → running → completed(문제 있으면completed_with_issues), 취소 요청 시cancel_requested → cancelled(enum.ts:36-45). 항목은 100건씩 materialize 하고(service.ts:1167), 조직당 최대 3건·전역 최대 10건 동시 처리해요(service.ts:59-60). - 배치 항목 상태:
pending·claimed·succeeded·failed·cancelled·skipped(enum.ts:47-54).
메시지 (Works 메신저 연동)
AnnualLeavePromotionMessagePurpose (enum.ts:30-34) 3종이 워크플로 메시지로 발행돼요.
| 목적 | 발행 시점 | 제목 |
|---|---|---|
first | 1차 안내 | 연차 사용촉진 안내 — 잔여 연차·제출 기한 안내 + 「서명하기」 액션 (service.ts:874) |
reminder | 재안내 | 연차 사용촉진 서명·계획서 제출 안내 (service.ts:1598) |
second | 2차 지정 | 연차 사용시기 지정 안내 — 지정 일정 포함 (service.ts:2073) |
- 1차·재안내 메시지는
annual_leave_promotion_sign액션(promotionId + signAttemptId)을 실어 직원이 메신저에서 바로 서명 화면으로 진입할 수 있어요. - 메시지 본문은 "계획서 작성만으로 연차가 신청되거나 잔여 연차가 차감되지는 않아요" 를 명시해요(
service.ts:876).
⚠️ 구현 현황 · 하드닝 후보 (2026-08-13)
CODE가 진실이에요.
서명·계획서·2차 지정 모두 실제 연차 차감/신청으로 이어지지 않음 [판단 필요]
- 1차 서명 완료(
applied)는AnnualLeavePromotionPlanItem에 계획만 기록하고, 2차 지정(second_designated)도 지정 일정을AnnualLeavePromotionPlanItem에 기록할 뿐이에요(service.ts:2079-2096). 두 경로 모두LeaveRequest(휴가 신청)를 만들거나 연차 잔여를 차감하지 않아요 — 메시지 본문에도 "연차 신청이나 잔여 차감 처리가 아니에요" 로 명시(service.ts:2074). - 즉 촉진 절차의 기록·통지는 구현됐지만, 지정된 사용시기가 도래했을 때 실제 사용/차감으로 자동 연결되는 로직은 없어요. 법적 효과(미사용분 보상의무 면제)를 시스템이 어디까지 보증하는지는 제품·노무 판단이 필요해요.
기한 초과 자동 취소는 스케줄러 트리거에 의존
expireDuePromotions(service.ts:1914)와 배치 처리processAnnualLeavePromotionBatches(service.ts:1264)는 주기 실행(크론/워커)에 의존해요. 트리거가 멈추면awaiting_signature건이 기한이 지나도expired_cancelled로 넘어가지 않고, 배치도 진행되지 않아요.
정책 문서 신설 (이 문서)
- 이 기능은 라우터 15개 프로시저 +
AnnualLeavePromotion*엔티티군 + enum 다수로 구현돼 있었으나 정책 문서가 없었어요. 이 문서로 실제 구현을 문서화했어요.