Files
TK-FB-Project/api.hyungi.net/models/monthlyStatusModel.js
Hyungi Ahn 746e09420b feat: 캘린더 기반 작업 현황 확인 시스템 구현
- 월별 캘린더 UI로 작업 현황을 한눈에 확인 가능
- 미입력(빨강), 부분입력(주황), 확인필요(보라), 이상없음(초록) 상태 표시
- 범례 아이콘(●)을 사용한 직관적인 상태 표시
- 날짜 클릭 시 해당일 작업자별 상세 현황 모달
- 작업자 클릭 시 개별 작업 입력/수정 모달
- 휴가 처리 기능 (연차, 반차, 반반차, 조퇴)
- 월별 집계 데이터 최적화로 API 호출 최소화

백엔드:
- monthly_worker_status, monthly_summary 테이블 추가
- 자동 집계 stored procedure 및 trigger 구현
- 확인필요(12시간 초과) 상태 감지 로직
- 출석 관리 시스템 확장

프론트엔드:
- 캘린더 그리드 UI 구현
- 상태별 색상 및 아이콘 표시
- 모달 기반 상세 정보 표시
- 반응형 디자인 적용
2025-11-04 10:12:07 +09:00

157 lines
4.3 KiB
JavaScript

// models/monthlyStatusModel.js
// 월별 작업자 상태 집계 모델
const { getDb } = require('../dbPool');
class MonthlyStatusModel {
// 월별 일자별 요약 조회 (캘린더용)
static async getMonthlySummary(year, month) {
const db = await getDb();
try {
const [rows] = await db.execute(`
SELECT
date,
total_workers,
working_workers,
incomplete_workers,
partial_workers,
complete_workers,
overtime_workers,
vacation_workers,
error_workers,
overtime_warning_workers,
total_work_hours,
total_work_count,
total_error_count,
has_issues,
has_errors,
has_overtime_warning,
last_updated
FROM monthly_summary
WHERE year = ? AND month = ?
ORDER BY date ASC
`, [year, month]);
return rows;
} catch (error) {
console.error('월별 요약 조회 오류:', error);
throw error;
}
}
// 특정 날짜의 작업자별 상태 조회 (모달용)
static async getDailyWorkerStatus(date) {
const db = await getDb();
try {
const [rows] = await db.execute(`
SELECT
mws.*,
w.worker_name,
w.job_type
FROM monthly_worker_status mws
JOIN workers w ON mws.worker_id = w.worker_id
WHERE mws.date = ?
ORDER BY w.worker_name ASC
`, [date]);
return rows;
} catch (error) {
console.error('일별 작업자 상태 조회 오류:', error);
throw error;
}
}
// 월별 집계 데이터 강제 재계산 (관리용)
static async recalculateMonth(year, month) {
const db = await getDb();
try {
// 해당 월의 모든 날짜와 작업자 조합을 찾아서 재계산
const [workDates] = await db.execute(`
SELECT DISTINCT report_date, worker_id
FROM daily_work_reports
WHERE YEAR(report_date) = ? AND MONTH(report_date) = ?
`, [year, month]);
let updatedCount = 0;
for (const { report_date, worker_id } of workDates) {
await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [report_date, worker_id]);
updatedCount++;
}
console.log(`${year}${month}월 집계 재계산 완료: ${updatedCount}`);
return { success: true, updatedCount };
} catch (error) {
console.error('월별 집계 재계산 오류:', error);
throw error;
}
}
// 특정 날짜 집계 강제 업데이트
static async updateDateSummary(date, workerId = null) {
const db = await getDb();
try {
if (workerId) {
// 특정 작업자만 업데이트
await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, workerId]);
} else {
// 해당 날짜의 모든 작업자 업데이트
const [workers] = await db.execute(`
SELECT DISTINCT worker_id
FROM daily_work_reports
WHERE report_date = ?
`, [date]);
for (const { worker_id } of workers) {
await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, worker_id]);
}
}
return { success: true };
} catch (error) {
console.error('날짜별 집계 업데이트 오류:', error);
throw error;
}
}
// 집계 테이블 상태 확인
static async getStatusInfo() {
const db = await getDb();
try {
const [summaryCount] = await db.execute(`
SELECT
COUNT(*) as total_days,
MIN(date) as earliest_date,
MAX(date) as latest_date,
MAX(last_updated) as last_update
FROM monthly_summary
`);
const [workerStatusCount] = await db.execute(`
SELECT
COUNT(*) as total_records,
COUNT(DISTINCT worker_id) as unique_workers,
COUNT(DISTINCT date) as unique_dates,
MAX(last_updated) as last_update
FROM monthly_worker_status
`);
return {
summary: summaryCount[0],
workerStatus: workerStatusCount[0]
};
} catch (error) {
console.error('집계 테이블 상태 확인 오류:', error);
throw error;
}
}
}
module.exports = MonthlyStatusModel;