feat(attendance): 주말+회사 휴무일 통합 처리
- monthlyComparisonModel: getCompanyHolidays 추가 - monthlyComparisonController: isHoliday에 company_holidays 포함 + holiday_name - proxyInputModel: getDailyStatus에 is_holiday/holiday_name 추가 - proxy-input.js: 휴무일 배너 + both_missing 작업자 비활성화 (특근자는 활성 유지) - 마이그레이션: 2026년 공휴일 16건 일괄 INSERT Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,7 +128,32 @@ async function loadWorkers() {
|
||||
}
|
||||
if (!res || !res.success) { cardsEl.innerHTML = '<div class="pi-empty"><p>데이터를 불러올 수 없습니다</p></div>'; return; }
|
||||
|
||||
// 휴무일 정보 저장
|
||||
window._isHoliday = res.data.is_holiday || false;
|
||||
window._holidayName = res.data.holiday_name || null;
|
||||
|
||||
// 휴무일 뱃지 표시
|
||||
var holidayBanner = document.getElementById('holidayBanner');
|
||||
if (holidayBanner) {
|
||||
if (window._isHoliday) {
|
||||
holidayBanner.classList.remove('hidden');
|
||||
holidayBanner.textContent = '휴무일' + (window._holidayName ? ' (' + window._holidayName + ')' : '');
|
||||
} else {
|
||||
holidayBanner.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
missingWorkers = (res.data.workers || []).filter(w => w.status !== 'complete');
|
||||
|
||||
// 휴무일에 both_missing인 작업자에게 휴무 플래그 추가
|
||||
if (window._isHoliday) {
|
||||
missingWorkers.forEach(function(w) {
|
||||
if (w.status === 'both_missing' && !w.vacation_type_code) {
|
||||
w._isHolidayOff = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('missingNum').textContent = missingWorkers.length;
|
||||
|
||||
if (missingWorkers.length === 0) {
|
||||
@@ -147,8 +172,10 @@ function renderCards() {
|
||||
const cardsEl = document.getElementById('workerCards');
|
||||
cardsEl.innerHTML = missingWorkers.map(w => {
|
||||
const isFullVacation = w.vacation_type_code === 'ANNUAL_FULL';
|
||||
const isHolidayOff = !!w._isHolidayOff;
|
||||
const isDisabled = isFullVacation || isHolidayOff;
|
||||
const hasVacation = !!w.vacation_type_code;
|
||||
const statusLabel = isFullVacation ? ''
|
||||
const statusLabel = isDisabled ? ''
|
||||
: ({ 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) {
|
||||
@@ -156,13 +183,14 @@ function renderCards() {
|
||||
}
|
||||
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' : '';
|
||||
var badgeText = isHolidayOff ? '휴무' : (hasVacation ? w.vacation_type_name : '');
|
||||
const vacBadge = badgeText ? '<span class="pi-vac-badge">' + escHtml(badgeText) + '</span>' : '';
|
||||
const disabledClass = isDisabled ? ' vacation-disabled' : '';
|
||||
|
||||
return `
|
||||
<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">${isFullVacation ? '' : (sel ? '<i class="fas fa-check text-xs"></i>' : '')}</div>
|
||||
<div class="pi-card-check">${isDisabled ? '' : (sel ? '<i class="fas fa-check text-xs"></i>' : '')}</div>
|
||||
<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>
|
||||
@@ -237,7 +265,7 @@ function escHtml(s) { return (s || '').replace(/&/g, '&').replace(/</g, '<
|
||||
// ===== 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 (worker && (worker.vacation_type_code === 'ANNUAL_FULL' || worker._isHolidayOff)) return;
|
||||
if (selectedIds.has(userId)) {
|
||||
selectedIds.delete(userId);
|
||||
} else {
|
||||
@@ -307,10 +335,10 @@ function applyBulk(field, value) {
|
||||
|
||||
if (hasExisting) {
|
||||
if (!confirm('이미 입력된 값이 있습니다. 덮어쓰시겠습니까?')) {
|
||||
// 빈 필드만 채움 (연차 작업자 skip)
|
||||
// 빈 필드만 채움 (연차/휴무 작업자 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 (bw && (bw.vacation_type_code === 'ANNUAL_FULL' || bw._isHolidayOff)) continue;
|
||||
if (!workerFormData[uid][field] || workerFormData[uid][field] === '') {
|
||||
workerFormData[uid][field] = value;
|
||||
}
|
||||
@@ -323,7 +351,7 @@ 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;
|
||||
if (bw2 && (bw2.vacation_type_code === 'ANNUAL_FULL' || bw2._isHolidayOff)) continue;
|
||||
workerFormData[uid][field] = value;
|
||||
if (field === 'work_type_id') workerFormData[uid].task_id = '';
|
||||
}
|
||||
@@ -354,7 +382,7 @@ async function saveProxyInput() {
|
||||
// 연차 작업자 선택 해제 (안전장치)
|
||||
for (const uid of selectedIds) {
|
||||
const ww = missingWorkers.find(x => x.user_id === uid);
|
||||
if (ww && ww.vacation_type_code === 'ANNUAL_FULL') {
|
||||
if (ww && (ww.vacation_type_code === 'ANNUAL_FULL' || ww._isHolidayOff)) {
|
||||
selectedIds.delete(uid);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user