feat(sprint005): 월간 확인 워크플로우 — 관리자 확인요청 + 수정요청
- DB: status ENUM 확장 (review_sent, change_request) + reviewed_by/at, change_details - API: POST /review-send (일괄 확인요청), POST /review-respond (수정 승인/거부) - 작업자: pending=검토대기, review_sent=확인/수정요청, rejected=동의(재확인) - 관리자: 필터 탭 확장 + 확인요청 일괄 발송 버튼 - confirm 상태 전환 검증: pending→confirmed 차단 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -220,30 +220,62 @@ function renderConfirmStatus(conf) {
|
||||
var actions = document.getElementById('bottomActions');
|
||||
var statusEl = document.getElementById('confirmedStatus');
|
||||
var badge = document.getElementById('statusBadge');
|
||||
var confirmBtn = document.getElementById('confirmBtn');
|
||||
var rejectBtn = document.getElementById('rejectBtn');
|
||||
var status = conf ? conf.status : 'pending';
|
||||
|
||||
if (!conf || conf.status === 'pending') {
|
||||
actions.classList.remove('hidden');
|
||||
statusEl.classList.add('hidden');
|
||||
badge.textContent = '미확인';
|
||||
// 기본: 버튼 숨김 + 상태 숨김
|
||||
actions.classList.add('hidden');
|
||||
statusEl.classList.add('hidden');
|
||||
|
||||
if (status === 'pending') {
|
||||
badge.textContent = '검토대기';
|
||||
badge.className = 'mmc-status-badge pending';
|
||||
return;
|
||||
}
|
||||
|
||||
if (conf.status === 'confirmed') {
|
||||
actions.classList.add('hidden');
|
||||
statusEl.classList.remove('hidden');
|
||||
document.getElementById('confirmedText').textContent = '관리자 검토 대기 중입니다';
|
||||
} else if (status === 'review_sent') {
|
||||
badge.textContent = '확인요청';
|
||||
badge.className = 'mmc-status-badge review_sent';
|
||||
actions.classList.remove('hidden');
|
||||
confirmBtn.innerHTML = '<i class="fas fa-check-circle mr-2"></i>확인 완료';
|
||||
rejectBtn.innerHTML = '<i class="fas fa-edit mr-2"></i>수정요청';
|
||||
rejectBtn.onclick = function() { openChangeRequestModal(); };
|
||||
} else if (status === 'confirmed') {
|
||||
badge.textContent = '확인완료';
|
||||
badge.className = 'mmc-status-badge confirmed';
|
||||
statusEl.classList.remove('hidden');
|
||||
var dt = conf.confirmed_at ? new Date(conf.confirmed_at).toLocaleDateString('ko') : '';
|
||||
document.getElementById('confirmedText').textContent = dt + ' 확인 완료';
|
||||
} else if (conf.status === 'rejected') {
|
||||
actions.classList.remove('hidden');
|
||||
statusEl.classList.add('hidden');
|
||||
} else if (status === 'change_request') {
|
||||
badge.textContent = '수정요청';
|
||||
badge.className = 'mmc-status-badge change_request';
|
||||
statusEl.classList.remove('hidden');
|
||||
document.getElementById('confirmedText').textContent = '수정요청이 제출되었습니다. 관리자 확인 대기 중';
|
||||
} else if (status === 'rejected') {
|
||||
badge.textContent = '반려';
|
||||
badge.className = 'mmc-status-badge rejected';
|
||||
actions.classList.remove('hidden');
|
||||
confirmBtn.innerHTML = '<i class="fas fa-check-circle mr-2"></i>동의(재확인)';
|
||||
rejectBtn.classList.add('hidden');
|
||||
statusEl.classList.remove('hidden');
|
||||
document.getElementById('confirmedText').textContent = '반려 사유: ' + (conf.reject_reason || '-') + '\n반려 사유를 확인하고 동의하시면 확인 완료 버튼을 눌러주세요.';
|
||||
}
|
||||
}
|
||||
|
||||
function openChangeRequestModal() {
|
||||
document.getElementById('rejectReason').value = '';
|
||||
document.getElementById('rejectModal').classList.remove('hidden');
|
||||
// 모달 제목/버튼 수정요청용으로 변경
|
||||
var header = document.querySelector('.mmc-modal-header span');
|
||||
if (header) header.innerHTML = '<i class="fas fa-edit text-blue-500 mr-2"></i>수정요청';
|
||||
var submitBtn = document.querySelector('.mmc-modal-submit');
|
||||
if (submitBtn) submitBtn.textContent = '수정요청 제출';
|
||||
var desc = document.querySelector('.mmc-modal-desc');
|
||||
if (desc) desc.textContent = '수정이 필요한 내용을 입력해주세요:';
|
||||
var note = document.querySelector('.mmc-modal-note');
|
||||
if (note) note.innerHTML = '<i class="fas fa-info-circle text-blue-400 mr-1"></i>수정요청 시 관리자에게 알림이 전달됩니다.';
|
||||
}
|
||||
|
||||
// ===== Actions =====
|
||||
async function confirmMonth() {
|
||||
if (isProcessing) return;
|
||||
@@ -268,13 +300,14 @@ function closeRejectModal() { document.getElementById('rejectModal').classList.a
|
||||
async function submitReject() {
|
||||
if (isProcessing) return;
|
||||
var reason = document.getElementById('rejectReason').value.trim();
|
||||
if (!reason) { showToast('반려 사유를 입력해주세요', 'error'); return; }
|
||||
if (!reason) { showToast('수정 내용을 입력해주세요', 'error'); return; }
|
||||
isProcessing = true;
|
||||
try {
|
||||
var res = await window.apiCall('/monthly-comparison/confirm', 'POST', {
|
||||
year: currentYear, month: currentMonth, status: 'rejected', reject_reason: reason
|
||||
year: currentYear, month: currentMonth, status: 'change_request',
|
||||
change_details: { description: reason }
|
||||
});
|
||||
if (res && res.success) { showToast(res.message || '반려 제출 완료', 'success'); closeRejectModal(); loadData(); }
|
||||
if (res && res.success) { showToast(res.message || '수정요청 완료', 'success'); closeRejectModal(); loadData(); }
|
||||
else { showToast(res && res.message || '처리 실패', 'error'); }
|
||||
} catch (e) { showToast('네트워크 오류', 'error'); }
|
||||
finally { isProcessing = false; }
|
||||
|
||||
Reference in New Issue
Block a user