diff --git a/api.hyungi.net/controllers/monthlyStatusController.js b/api.hyungi.net/controllers/monthlyStatusController.js index 21ff297..c34c5df 100644 --- a/api.hyungi.net/controllers/monthlyStatusController.js +++ b/api.hyungi.net/controllers/monthlyStatusController.js @@ -1,201 +1,231 @@ -// controllers/monthlyStatusController.js -// 월별 작업자 상태 집계 컨트롤러 +/** + * 월별 작업자 상태 집계 컨트롤러 + * + * 월별 캘린더 및 작업자 상태 집계 API 엔드포인트 핸들러 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ const MonthlyStatusModel = require('../models/monthlyStatusModel'); +const { ValidationError, ForbiddenError, DatabaseError } = require('../utils/errors'); +const { asyncHandler } = require('../middlewares/errorHandler'); +const logger = require('../utils/logger'); -class MonthlyStatusController { - // 월별 캘린더 데이터 조회 - static async getMonthlyCalendarData(req, res) { - try { - const { year, month } = req.query; - - if (!year || !month) { - return res.status(400).json({ - success: false, - message: '연도(year)와 월(month)이 필요합니다.' - }); - } - - const yearNum = parseInt(year); - const monthNum = parseInt(month); - - if (yearNum < 2020 || yearNum > 2030 || monthNum < 1 || monthNum > 12) { - return res.status(400).json({ - success: false, - message: '유효하지 않은 연도 또는 월입니다.' - }); - } - - console.log(`📅 월별 캘린더 데이터 조회: ${year}년 ${month}월`); - - const summaryData = await MonthlyStatusModel.getMonthlySummary(yearNum, monthNum); - - // 날짜별 객체로 변환 (날짜 키를 YYYY-MM-DD 형식으로 변환) - const calendarData = {}; - summaryData.forEach(day => { - const dateKey = day.date.toISOString().split('T')[0]; // YYYY-MM-DD 형식으로 변환 - calendarData[dateKey] = { - totalWorkers: day.total_workers, - workingWorkers: day.working_workers, - hasIssues: day.has_issues, - hasErrors: day.has_errors, - hasOvertimeWarning: day.has_overtime_warning, - incompleteWorkers: day.incomplete_workers, - partialWorkers: day.partial_workers, - errorWorkers: day.error_workers, - overtimeWarningWorkers: day.overtime_warning_workers, - totalHours: parseFloat(day.total_work_hours || 0), - totalTasks: day.total_work_count, - errorCount: day.total_error_count, - lastUpdated: day.last_updated - }; - }); - - res.json({ - success: true, - data: calendarData, - message: `${year}년 ${month}월 캘린더 데이터를 성공적으로 조회했습니다.` - }); - - } catch (error) { - console.error('월별 캘린더 데이터 조회 오류:', error); - res.status(500).json({ - success: false, - message: '월별 캘린더 데이터 조회 중 오류가 발생했습니다.', - error: error.message - }); - } +/** + * 월별 캘린더 데이터 조회 + */ +const getMonthlyCalendarData = asyncHandler(async (req, res) => { + const { year, month } = req.query; + + if (!year || !month) { + throw new ValidationError('연도(year)와 월(month)이 필요합니다', { + required: ['year', 'month'], + received: { year, month } + }); } - - // 특정 날짜의 작업자별 상세 상태 조회 - static async getDailyWorkerDetails(req, res) { - try { - const { date } = req.query; - - if (!date) { - return res.status(400).json({ - success: false, - message: '날짜(date)가 필요합니다.' - }); - } - - console.log(`👥 일별 작업자 상세 조회: ${date}`); - - const workerDetails = await MonthlyStatusModel.getDailyWorkerStatus(date); - - // 프론트엔드에서 사용하기 쉽도록 데이터 변환 - const formattedData = workerDetails.map(worker => ({ - workerId: worker.worker_id, - workerName: worker.worker_name, - jobType: worker.job_type, - totalHours: parseFloat(worker.total_work_hours || 0), - actualWorkHours: parseFloat(worker.actual_work_hours || 0), - vacationHours: parseFloat(worker.vacation_hours || 0), - totalWorkCount: worker.total_work_count, - regularWorkCount: worker.regular_work_count, - errorWorkCount: worker.error_work_count, - status: worker.work_status, - hasVacation: worker.has_vacation, - hasError: worker.has_error, - hasIssues: worker.has_issues, - lastUpdated: worker.last_updated - })); - - // 요약 정보 계산 - const summary = { - totalWorkers: formattedData.length, - totalHours: formattedData.reduce((sum, w) => sum + w.totalHours, 0), - totalTasks: formattedData.reduce((sum, w) => sum + w.totalWorkCount, 0), - errorCount: formattedData.reduce((sum, w) => sum + w.errorWorkCount, 0) + + const yearNum = parseInt(year); + const monthNum = parseInt(month); + + if (yearNum < 2020 || yearNum > 2030 || monthNum < 1 || monthNum > 12) { + throw new ValidationError('유효하지 않은 연도 또는 월입니다', { + received: { year: yearNum, month: monthNum } + }); + } + + logger.info('월별 캘린더 데이터 조회 요청', { year: yearNum, month: monthNum }); + + try { + const summaryData = await MonthlyStatusModel.getMonthlySummary(yearNum, monthNum); + + // 날짜별 객체로 변환 + const calendarData = {}; + summaryData.forEach(day => { + const dateKey = day.date.toISOString().split('T')[0]; + calendarData[dateKey] = { + totalWorkers: day.total_workers, + workingWorkers: day.working_workers, + hasIssues: day.has_issues, + hasErrors: day.has_errors, + hasOvertimeWarning: day.has_overtime_warning, + incompleteWorkers: day.incomplete_workers, + partialWorkers: day.partial_workers, + errorWorkers: day.error_workers, + overtimeWarningWorkers: day.overtime_warning_workers, + totalHours: parseFloat(day.total_work_hours || 0), + totalTasks: day.total_work_count, + errorCount: day.total_error_count, + lastUpdated: day.last_updated }; - - res.json({ - success: true, - data: { - workers: formattedData, - summary - }, - message: `${date} 작업자 상세 정보를 성공적으로 조회했습니다.` - }); - - } catch (error) { - console.error('일별 작업자 상세 조회 오류:', error); - res.status(500).json({ - success: false, - message: '일별 작업자 상세 조회 중 오류가 발생했습니다.', - error: error.message - }); - } - } - - // 월별 집계 재계산 (관리자용) - static async recalculateMonth(req, res) { - try { - const { year, month } = req.body; - - if (!year || !month) { - return res.status(400).json({ - success: false, - message: '연도(year)와 월(month)이 필요합니다.' - }); - } - - // 관리자 권한 확인 - if (req.user.role !== 'admin' && req.user.role !== 'system') { - return res.status(403).json({ - success: false, - message: '관리자 권한이 필요합니다.' - }); - } - - console.log(`🔄 월별 집계 재계산 시작: ${year}년 ${month}월`); - - const result = await MonthlyStatusModel.recalculateMonth(parseInt(year), parseInt(month)); - - res.json({ - success: true, - data: result, - message: `${year}년 ${month}월 집계 재계산이 완료되었습니다.` - }); - - } catch (error) { - console.error('월별 집계 재계산 오류:', error); - res.status(500).json({ - success: false, - message: '월별 집계 재계산 중 오류가 발생했습니다.', - error: error.message - }); - } - } - - // 집계 테이블 상태 확인 (관리자용) - static async getStatusInfo(req, res) { - try { - // 관리자 권한 확인 - if (req.user.role !== 'admin' && req.user.role !== 'system') { - return res.status(403).json({ - success: false, - message: '관리자 권한이 필요합니다.' - }); - } - - const statusInfo = await MonthlyStatusModel.getStatusInfo(); - - res.json({ - success: true, - data: statusInfo, - message: '집계 테이블 상태 정보를 성공적으로 조회했습니다.' - }); - - } catch (error) { - console.error('집계 테이블 상태 확인 오류:', error); - res.status(500).json({ - success: false, - message: '집계 테이블 상태 확인 중 오류가 발생했습니다.', - error: error.message - }); - } - } -} + }); -module.exports = MonthlyStatusController; + logger.info('월별 캘린더 데이터 조회 성공', { + year: yearNum, + month: monthNum, + dayCount: Object.keys(calendarData).length + }); + + res.json({ + success: true, + data: calendarData, + message: `${year}년 ${month}월 캘린더 데이터를 성공적으로 조회했습니다` + }); + } catch (error) { + logger.error('월별 캘린더 데이터 조회 실패', { + year: yearNum, + month: monthNum, + error: error.message + }); + throw new DatabaseError('월별 캘린더 데이터 조회 중 오류가 발생했습니다'); + } +}); + +/** + * 특정 날짜의 작업자별 상세 상태 조회 + */ +const getDailyWorkerDetails = asyncHandler(async (req, res) => { + const { date } = req.query; + + if (!date) { + throw new ValidationError('날짜(date)가 필요합니다', { + required: ['date'], + received: { date } + }); + } + + logger.info('일별 작업자 상세 조회 요청', { date }); + + try { + const workerDetails = await MonthlyStatusModel.getDailyWorkerStatus(date); + + // 데이터 변환 + const formattedData = workerDetails.map(worker => ({ + workerId: worker.worker_id, + workerName: worker.worker_name, + jobType: worker.job_type, + totalHours: parseFloat(worker.total_work_hours || 0), + actualWorkHours: parseFloat(worker.actual_work_hours || 0), + vacationHours: parseFloat(worker.vacation_hours || 0), + totalWorkCount: worker.total_work_count, + regularWorkCount: worker.regular_work_count, + errorWorkCount: worker.error_work_count, + status: worker.work_status, + hasVacation: worker.has_vacation, + hasError: worker.has_error, + hasIssues: worker.has_issues, + lastUpdated: worker.last_updated + })); + + // 요약 정보 계산 + const summary = { + totalWorkers: formattedData.length, + totalHours: formattedData.reduce((sum, w) => sum + w.totalHours, 0), + totalTasks: formattedData.reduce((sum, w) => sum + w.totalWorkCount, 0), + errorCount: formattedData.reduce((sum, w) => sum + w.errorWorkCount, 0) + }; + + logger.info('일별 작업자 상세 조회 성공', { + date, + workerCount: formattedData.length, + totalHours: summary.totalHours + }); + + res.json({ + success: true, + data: { + workers: formattedData, + summary + }, + message: `${date} 작업자 상세 정보를 성공적으로 조회했습니다` + }); + } catch (error) { + logger.error('일별 작업자 상세 조회 실패', { + date, + error: error.message + }); + throw new DatabaseError('일별 작업자 상세 조회 중 오류가 발생했습니다'); + } +}); + +/** + * 월별 집계 재계산 (관리자용) + */ +const recalculateMonth = asyncHandler(async (req, res) => { + const { year, month } = req.body; + + if (!year || !month) { + throw new ValidationError('연도(year)와 월(month)이 필요합니다', { + required: ['year', 'month'], + received: { year, month } + }); + } + + // 관리자 권한 확인 + if (req.user.role !== 'admin' && req.user.role !== 'system') { + throw new ForbiddenError('관리자 권한이 필요합니다'); + } + + logger.info('월별 집계 재계산 시작', { + year, + month, + requestedBy: req.user.username + }); + + try { + const result = await MonthlyStatusModel.recalculateMonth(parseInt(year), parseInt(month)); + + logger.info('월별 집계 재계산 성공', { year, month, result }); + + res.json({ + success: true, + data: result, + message: `${year}년 ${month}월 집계 재계산이 완료되었습니다` + }); + } catch (error) { + logger.error('월별 집계 재계산 실패', { + year, + month, + error: error.message + }); + throw new DatabaseError('월별 집계 재계산 중 오류가 발생했습니다'); + } +}); + +/** + * 집계 테이블 상태 확인 (관리자용) + */ +const getStatusInfo = asyncHandler(async (req, res) => { + // 관리자 권한 확인 + if (req.user.role !== 'admin' && req.user.role !== 'system') { + throw new ForbiddenError('관리자 권한이 필요합니다'); + } + + logger.info('집계 테이블 상태 확인 요청', { + requestedBy: req.user.username + }); + + try { + const statusInfo = await MonthlyStatusModel.getStatusInfo(); + + logger.info('집계 테이블 상태 확인 성공'); + + res.json({ + success: true, + data: statusInfo, + message: '집계 테이블 상태 정보를 성공적으로 조회했습니다' + }); + } catch (error) { + logger.error('집계 테이블 상태 확인 실패', { + error: error.message + }); + throw new DatabaseError('집계 테이블 상태 확인 중 오류가 발생했습니다'); + } +}); + +module.exports = { + getMonthlyCalendarData, + getDailyWorkerDetails, + recalculateMonth, + getStatusInfo +};