fix(sprint004): 코드 리뷰 반영 — vacation_days 소수 + 이중제출 방지 + deprecated 테이블 전환
- monthlyComparisonModel: vacation_types.deduct_days AS vacation_days 추가 - monthlyComparisonController: vacationDays++ → parseFloat(attend.vacation_days) 소수 지원 - monthly-comparison.js: confirmMonth/submitReject 이중 제출 방지 (isProcessing 플래그) - vacationBalanceModel: create/update/delete/bulkCreate → sp_vacation_balances + balance_type 매핑 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -282,12 +282,33 @@ function renderConfirmationStatus(conf) {
|
||||
const statusEl = document.getElementById('confirmedStatus');
|
||||
const badge = document.getElementById('statusBadge');
|
||||
|
||||
if (!conf) { actions.classList.remove('hidden'); statusEl.classList.add('hidden'); return; }
|
||||
if (!conf) {
|
||||
// detail 모드(관리자가 타인의 기록 조회)에서는 버튼 숨김
|
||||
if (currentMode === 'detail') {
|
||||
actions.classList.add('hidden');
|
||||
} else {
|
||||
actions.classList.remove('hidden');
|
||||
}
|
||||
statusEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
badge.textContent = { pending: '미확인', confirmed: '확인완료', rejected: '반려' }[conf.status] || '';
|
||||
badge.className = `mc-status-badge ${conf.status}`;
|
||||
|
||||
if (conf.status === 'confirmed') {
|
||||
if (currentMode === 'detail') {
|
||||
// 관리자 상세 뷰: 확인/반려 버튼 숨기고 상태만 표시
|
||||
actions.classList.add('hidden');
|
||||
if (conf.status !== 'pending') {
|
||||
statusEl.classList.remove('hidden');
|
||||
const statusLabel = { confirmed: '확인 완료', rejected: '반려' }[conf.status] || '';
|
||||
const dt = conf.confirmed_at ? new Date(conf.confirmed_at).toLocaleString('ko') : '';
|
||||
const reason = conf.reject_reason ? ` (사유: ${conf.reject_reason})` : '';
|
||||
document.getElementById('confirmedText').textContent = `${dt} ${statusLabel}${reason}`;
|
||||
} else {
|
||||
statusEl.classList.add('hidden');
|
||||
}
|
||||
} else if (conf.status === 'confirmed') {
|
||||
actions.classList.add('hidden');
|
||||
statusEl.classList.remove('hidden');
|
||||
const dt = conf.confirmed_at ? new Date(conf.confirmed_at).toLocaleString('ko') : '';
|
||||
@@ -365,22 +386,30 @@ function filterWorkers(status) {
|
||||
function updateExportButton(summary, workers) {
|
||||
const btn = document.getElementById('exportBtn');
|
||||
const note = document.getElementById('exportNote');
|
||||
const pendingCount = (workers || []).filter(w => w.status === 'pending').length;
|
||||
const pendingCount = (workers || []).filter(w => !w.status || w.status === 'pending').length;
|
||||
const rejectedCount = (workers || []).filter(w => w.status === 'rejected').length;
|
||||
const allConfirmed = pendingCount === 0 && rejectedCount === 0;
|
||||
|
||||
if (pendingCount === 0) {
|
||||
if (allConfirmed) {
|
||||
btn.disabled = false;
|
||||
note.textContent = '모든 작업자가 확인을 완료했습니다';
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
const rejectedCount = (workers || []).filter(w => w.status === 'rejected').length;
|
||||
note.textContent = `${pendingCount}명 미확인${rejectedCount > 0 ? `, ${rejectedCount}명 반려` : ''} — 전원 확인 후 다운로드 가능합니다`;
|
||||
const parts = [];
|
||||
if (pendingCount > 0) parts.push(`${pendingCount}명 미확인`);
|
||||
if (rejectedCount > 0) parts.push(`${rejectedCount}명 반려`);
|
||||
note.textContent = `${parts.join(', ')} — 전원 확인 후 다운로드 가능합니다`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Actions =====
|
||||
let isProcessing = false;
|
||||
|
||||
async function confirmMonth() {
|
||||
if (isProcessing) return;
|
||||
if (!confirm(`${currentYear}년 ${currentMonth}월 근무 내역을 확인하시겠습니까?`)) return;
|
||||
|
||||
isProcessing = true;
|
||||
try {
|
||||
let res;
|
||||
if (MOCK_ENABLED) {
|
||||
@@ -399,6 +428,8 @@ async function confirmMonth() {
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('네트워크 오류', 'error');
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,12 +443,14 @@ function closeRejectModal() {
|
||||
}
|
||||
|
||||
async function submitReject() {
|
||||
if (isProcessing) return;
|
||||
const reason = document.getElementById('rejectReason').value.trim();
|
||||
if (!reason) {
|
||||
showToast('반려 사유를 입력해주세요', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
try {
|
||||
let res;
|
||||
if (MOCK_ENABLED) {
|
||||
@@ -437,6 +470,8 @@ async function submitReject() {
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('네트워크 오류', 'error');
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user