feat(vacation): 조퇴 연차 차감 처리 (0.75일 = 반차+반반차)

- EARLY_LEAVE vacation type 추가 (deduct_days=0.75)
- work-status: isLeave=true + 동적 vacation_type_id 조회 + 실패 보호
- annual-overview: 월별 요약 테이블에 조퇴 컬럼 추가 + 편집 드롭다운

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-31 10:17:32 +09:00
parent ec7699b270
commit 408bf1af62
3 changed files with 47 additions and 7 deletions

View File

@@ -308,13 +308,14 @@
let hasCheckinData = false;
let isAlreadySaved = false;
let isSaving = false;
let earlyLeaveTypeId = null;
const attendanceTypes = [
{ value: 'normal', label: '정시근무', hours: 8, isLeave: false },
{ value: 'annual', label: '연차', hours: 0, isLeave: true },
{ value: 'half', label: '반차', hours: 4, isLeave: true },
{ value: 'quarter', label: '반반차', hours: 6, isLeave: true },
{ value: 'early', label: '조퇴', hours: 2, isLeave: false },
{ value: 'early', label: '조퇴', hours: 2, isLeave: true },
{ value: 'overtime', label: '연장근로', hours: 8, isLeave: false }
];
@@ -347,6 +348,15 @@
if (!selectedDate) return alert('날짜를 선택해주세요.');
try {
// EARLY_LEAVE 유형 ID 조회 (최초 1회)
if (!earlyLeaveTypeId) {
try {
const vtRes = await axios.get('/attendance/vacation-types');
const earlyType = (vtRes.data.data || []).find(t => t.type_code === 'EARLY_LEAVE');
earlyLeaveTypeId = earlyType?.id || null;
} catch(e) {}
}
const [workersRes, recordsRes] = await Promise.all([
axios.get('/workers?limit=100'),
axios.get(`/attendance/daily-records?date=${selectedDate}`).catch(() => ({ data: { data: [] } }))
@@ -743,13 +753,27 @@
'overtime': 1 // NORMAL (시간으로 구분)
};
// vacation_types: 1=ANNUAL_FULL, 2=ANNUAL_HALF, 3=ANNUAL_QUARTER
// vacation_types: 1=ANNUAL_FULL, 2=ANNUAL_HALF, 3=ANNUAL_QUARTER, EARLY_LEAVE=동적
const vacationTypeIdMap = {
'annual': 1,
'half': 2,
'quarter': 3,
'early': earlyLeaveTypeId,
};
// 조퇴가 있는데 vacation_type_id가 없으면 저장 차단
const hasEarlyWithoutType = workers.some(w => {
const s = workStatus[w.user_id];
return s && s.type === 'early' && !earlyLeaveTypeId;
});
if (hasEarlyWithoutType) {
alert('조퇴 휴가 유형이 등록되지 않았습니다. 관리자에게 문의해주세요.');
isSaving = false;
saveBtn.disabled = false;
saveBtn.textContent = '저장';
return;
}
// 미입사자 제외하고 저장할 데이터 생성
const recordsToSave = workers
.filter(w => !workStatus[w.user_id]?.isNotHired)