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:
@@ -22,10 +22,11 @@ function determineStatus(report, attendance, isHoliday) {
|
||||
|
||||
// 날짜별 비교 데이터 생성
|
||||
async function buildComparisonData(userId, year, month) {
|
||||
const [reports, attendances, confirmation] = await Promise.all([
|
||||
const [reports, attendances, confirmation, holidays] = await Promise.all([
|
||||
Model.getWorkReports(userId, year, month),
|
||||
Model.getAttendanceRecords(userId, year, month),
|
||||
Model.getConfirmation(userId, year, month)
|
||||
Model.getConfirmation(userId, year, month),
|
||||
Model.getCompanyHolidays(year, month)
|
||||
]);
|
||||
|
||||
// 날짜 맵 생성
|
||||
@@ -57,7 +58,7 @@ async function buildComparisonData(userId, year, month) {
|
||||
const date = new Date(year, month - 1, day);
|
||||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
const dayOfWeek = date.getDay();
|
||||
const isHoliday = dayOfWeek === 0 || dayOfWeek === 6;
|
||||
const isHoliday = dayOfWeek === 0 || dayOfWeek === 6 || holidays.dateSet.has(dateStr);
|
||||
|
||||
const report = reportMap[dateStr] || null;
|
||||
const attend = attendMap[dateStr] || null;
|
||||
@@ -89,6 +90,7 @@ async function buildComparisonData(userId, year, month) {
|
||||
date: dateStr,
|
||||
day_of_week: DAYS_KR[dayOfWeek],
|
||||
is_holiday: isHoliday,
|
||||
holiday_name: holidays.nameMap[dateStr] || (dayOfWeek === 0 || dayOfWeek === 6 ? '주말' : null),
|
||||
work_report: report ? {
|
||||
total_hours: parseFloat(report.total_hours),
|
||||
entries: [{ project_name: report.project_names || '', work_type: report.work_type_names || '', hours: parseFloat(report.total_hours) }]
|
||||
|
||||
@@ -13,3 +13,20 @@ VALUES ('EARLY_LEAVE', '조퇴', 0.75, 1, 10);
|
||||
-- 작업자 월간 확인 페이지 등록
|
||||
INSERT IGNORE INTO pages (page_key, page_name, page_path, category, display_order)
|
||||
VALUES ('attendance.my_monthly_confirm', '월간 근무 확인', '/pages/attendance/my-monthly-confirm.html', '근태 관리', 25);
|
||||
-- 2026년 법정 공휴일 + 대체공휴일 일괄 등록
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-01-01', '신정', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-02-16', '설날 연휴', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-02-17', '설날', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-02-18', '설날 연휴', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-03-01', '삼일절', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-03-02', '대체공휴일(삼일절)', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-05-05', '어린이날', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-05-24', '석가탄신일', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-06-06', '현충일', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-08-15', '광복절', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-09-24', '추석 연휴', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-09-25', '추석', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-09-26', '추석 연휴', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-10-03', '개천절', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-10-09', '한글날', 'PAID', 1);
|
||||
INSERT IGNORE INTO company_holidays (holiday_date, holiday_name, holiday_type, created_by) VALUES ('2026-12-25', '크리스마스', 'PAID', 1);
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const MonthlyComparisonModel = {
|
||||
// 0. 해당 월의 회사 휴무일 조회
|
||||
async getCompanyHolidays(year, month) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT holiday_date, holiday_name FROM company_holidays
|
||||
WHERE YEAR(holiday_date) = ? AND MONTH(holiday_date) = ?`,
|
||||
[year, month]
|
||||
);
|
||||
const dateSet = new Set();
|
||||
const nameMap = {};
|
||||
rows.forEach(r => {
|
||||
const d = r.holiday_date instanceof Date
|
||||
? r.holiday_date.toISOString().split('T')[0]
|
||||
: String(r.holiday_date).split('T')[0];
|
||||
dateSet.add(d);
|
||||
nameMap[d] = r.holiday_name;
|
||||
});
|
||||
return { dateSet, nameMap };
|
||||
},
|
||||
|
||||
// 1. 작업보고서 일별 합산
|
||||
async getWorkReports(userId, year, month) {
|
||||
const db = await getDb();
|
||||
|
||||
@@ -110,6 +110,16 @@ const ProxyInputModel = {
|
||||
WHERE dar.record_date = ? AND dar.vacation_type_id IS NOT NULL
|
||||
`, [date]);
|
||||
|
||||
// 5. 해당 날짜가 회사 휴무일인지 확인
|
||||
const [holidayRows] = await db.query(
|
||||
`SELECT holiday_date, holiday_name FROM company_holidays WHERE holiday_date = ?`,
|
||||
[date]
|
||||
);
|
||||
const isCompanyHoliday = holidayRows.length > 0;
|
||||
const holidayName = isCompanyHoliday ? holidayRows[0].holiday_name : null;
|
||||
const dateObj = new Date(date);
|
||||
const isWeekend = dateObj.getDay() === 0 || dateObj.getDay() === 6;
|
||||
|
||||
// 메모리에서 조합
|
||||
const tbmMap = {};
|
||||
tbmAssignments.forEach(ta => {
|
||||
@@ -158,6 +168,8 @@ const ProxyInputModel = {
|
||||
|
||||
return {
|
||||
date,
|
||||
is_holiday: isWeekend || isCompanyHoliday,
|
||||
holiday_name: isCompanyHoliday ? holidayName : (isWeekend ? '주말' : null),
|
||||
summary: {
|
||||
total_active_workers: workers.length,
|
||||
tbm_completed: tbmCompleted,
|
||||
|
||||
Reference in New Issue
Block a user