All files / controllers monthlyStatusController.js

0% Statements 0/64
0% Branches 0/32
0% Functions 0/9
0% Lines 0/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
/**
 * 월별 작업자 상태 집계 컨트롤러
 *
 * 월별 캘린더 및 작업자 상태 집계 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');
 
/**
 * 월별 캘린더 데이터 조회
 */
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 }
    });
  }
 
  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
      };
    });
 
    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
};