diff --git a/system1-factory/api/models/proxyInputModel.js b/system1-factory/api/models/proxyInputModel.js index 2580d9d..6ff7488 100644 --- a/system1-factory/api/models/proxyInputModel.js +++ b/system1-factory/api/models/proxyInputModel.js @@ -99,6 +99,17 @@ const ProxyInputModel = { GROUP BY dwr.user_id `, [date]); + // 4. 해당 날짜의 연차 기록 + const [vacationRecords] = await db.query(` + SELECT dar.user_id, dar.vacation_type_id, + vt.type_code AS vacation_type_code, + vt.type_name AS vacation_type_name, + vt.deduct_days + FROM daily_attendance_records dar + JOIN vacation_types vt ON dar.vacation_type_id = vt.id + WHERE dar.record_date = ? AND dar.vacation_type_id IS NOT NULL + `, [date]); + // 메모리에서 조합 const tbmMap = {}; tbmAssignments.forEach(ta => { @@ -109,6 +120,9 @@ const ProxyInputModel = { const reportMap = {}; reports.forEach(r => { reportMap[r.user_id] = r; }); + const vacMap = {}; + vacationRecords.forEach(v => { vacMap[v.user_id] = v; }); + let tbmCompleted = 0, reportCompleted = 0, bothCompleted = 0, bothMissing = 0; const workerList = workers.map(w => { @@ -120,6 +134,7 @@ const ProxyInputModel = { is_proxy_input: !!ta.is_proxy_input })); const totalReportHours = reportMap[w.user_id]?.total_hours || 0; + const vac = vacMap[w.user_id] || null; let status = 'both_missing'; if (hasTbm && hasReport) { status = 'complete'; bothCompleted++; } @@ -133,7 +148,11 @@ const ProxyInputModel = { return { user_id: w.user_id, worker_name: w.worker_name, job_type: w.job_type, department_name: w.department_name, has_tbm: hasTbm, has_report: hasReport, - tbm_sessions: tbmSessions, total_report_hours: totalReportHours, status + tbm_sessions: tbmSessions, total_report_hours: totalReportHours, status, + vacation_type_id: vac ? vac.vacation_type_id : null, + vacation_type_code: vac ? vac.vacation_type_code : null, + vacation_type_name: vac ? vac.vacation_type_name : null, + vacation_hours: vac ? (8 - parseFloat(vac.deduct_days) * 8) : null }; }); diff --git a/system1-factory/web/css/proxy-input.css b/system1-factory/web/css/proxy-input.css index fb8fdcb..a505968 100644 --- a/system1-factory/web/css/proxy-input.css +++ b/system1-factory/web/css/proxy-input.css @@ -226,6 +226,17 @@ margin-bottom: 6px; } @keyframes ds-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } + +/* 연차 비활성화 */ +.pi-card.vacation-disabled { opacity: 0.5; } +.pi-card.vacation-disabled .pi-card-form { pointer-events: none; } +.pi-card.vacation-disabled .pi-card-header { cursor: default; } +.pi-vac-badge { + font-size: 0.65rem; font-weight: 600; + padding: 2px 6px; border-radius: 4px; + background: #dcfce7; color: #166534; + margin-left: 4px; +} .ds-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 48px 16px; color: #9ca3af; font-size: 0.875rem; } .ds-link { color: #2563eb; font-size: 0.8rem; text-decoration: underline; } diff --git a/system1-factory/web/js/proxy-input.js b/system1-factory/web/js/proxy-input.js index 5b81dfc..69273db 100644 --- a/system1-factory/web/js/proxy-input.js +++ b/system1-factory/web/js/proxy-input.js @@ -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 ? '' + escHtml(w.vacation_type_name) + '' : ''; + const disabledClass = isFullVacation ? ' vacation-disabled' : ''; return ` -