feat(proxy-input): 연차 정보 연동 — 연차 작업자 비활성화 + 뱃지

- 모델: getDailyStatus에 vacation_type 쿼리 추가
- 프론트: 연차(ANNUAL_FULL) 카드 비활성화 + 선택/일괄설정/저장에서 제외
- 반차/반반차/조퇴: 뱃지 표시 + 근무시간 자동 조정 (4h/6h/2h)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-31 13:09:05 +09:00
parent 492843342a
commit 755e4142e1
4 changed files with 63 additions and 10 deletions

View File

@@ -146,17 +146,25 @@ async function loadWorkers() {
function renderCards() {
const cardsEl = document.getElementById('workerCards');
cardsEl.innerHTML = missingWorkers.map(w => {
const statusLabel = { both_missing: 'TBM+보고서 미입력', tbm_only: '보고서만 미입력', report_only: 'TBM만 미입력' }[w.status] || '';
const isFullVacation = w.vacation_type_code === 'ANNUAL_FULL';
const hasVacation = !!w.vacation_type_code;
const statusLabel = isFullVacation ? ''
: ({ both_missing: 'TBM+보고서 미입력', tbm_only: '보고서만 미입력', report_only: 'TBM만 미입력' }[w.status] || '');
const fd = workerFormData[w.user_id] || getDefaultFormData(w);
if (hasVacation && !isFullVacation && w.vacation_hours != null) {
fd.work_hours = w.vacation_hours;
}
workerFormData[w.user_id] = fd;
const sel = selectedIds.has(w.user_id);
const vacBadge = hasVacation ? '<span class="pi-vac-badge">' + escHtml(w.vacation_type_name) + '</span>' : '';
const disabledClass = isFullVacation ? ' vacation-disabled' : '';
return `
<div class="pi-card ${sel ? 'selected' : ''}" id="card-${w.user_id}">
<div class="pi-card ${sel ? 'selected' : ''}${disabledClass}" id="card-${w.user_id}">
<div class="pi-card-header" onclick="toggleWorker(${w.user_id})">
<div class="pi-card-check">${sel ? '<i class="fas fa-check text-xs"></i>' : ''}</div>
<div class="pi-card-check">${isFullVacation ? '' : (sel ? '<i class="fas fa-check text-xs"></i>' : '')}</div>
<div>
<div class="pi-card-name">${escHtml(w.worker_name)}</div>
<div class="pi-card-name">${escHtml(w.worker_name)} ${vacBadge}</div>
<div class="pi-card-meta">${escHtml(w.job_type)} · ${escHtml(w.department_name)}</div>
</div>
<div class="pi-card-status ${w.status}">${statusLabel}</div>
@@ -228,6 +236,8 @@ function escHtml(s) { return (s || '').replace(/&/g, '&amp;').replace(/</g, '&lt
// ===== Worker Toggle =====
function toggleWorker(userId) {
var worker = missingWorkers.find(function(w) { return w.user_id === userId; });
if (worker && worker.vacation_type_code === 'ANNUAL_FULL') return;
if (selectedIds.has(userId)) {
selectedIds.delete(userId);
} else {
@@ -297,8 +307,10 @@ function applyBulk(field, value) {
if (hasExisting) {
if (!confirm('이미 입력된 값이 있습니다. 덮어쓰시겠습니까?')) {
// 빈 필드만 채움
// 빈 필드만 채움 (연차 작업자 skip)
for (const uid of selectedIds) {
var bw = missingWorkers.find(function(w) { return w.user_id === uid; });
if (bw && bw.vacation_type_code === 'ANNUAL_FULL') continue;
if (!workerFormData[uid][field] || workerFormData[uid][field] === '') {
workerFormData[uid][field] = value;
}
@@ -310,6 +322,8 @@ function applyBulk(field, value) {
}
for (const uid of selectedIds) {
var bw2 = missingWorkers.find(function(w) { return w.user_id === uid; });
if (bw2 && bw2.vacation_type_code === 'ANNUAL_FULL') continue;
workerFormData[uid][field] = value;
if (field === 'work_type_id') workerFormData[uid].task_id = '';
}
@@ -337,6 +351,15 @@ function updateSaveBtn() {
async function saveProxyInput() {
if (saving || selectedIds.size === 0) return;
// 연차 작업자 선택 해제 (안전장치)
for (const uid of selectedIds) {
const ww = missingWorkers.find(x => x.user_id === uid);
if (ww && ww.vacation_type_code === 'ANNUAL_FULL') {
selectedIds.delete(uid);
}
}
if (selectedIds.size === 0) { showToast('저장할 대상이 없습니다', 'error'); return; }
// 유효성 검사
for (const uid of selectedIds) {
const fd = workerFormData[uid];