Files
TK-FB-Project/api.hyungi.net/controllers/dailyWorkReportController.js
Hyungi Ahn 34d38b2938 refactor: dailyWorkReportController.js 부분 리팩토링 적용
- 새로운 에러 처리 시스템 적용:
  * asyncHandler로 비동기 함수 래핑
  * ApiError 클래스로 유효성 검사 에러 처리
  * handleDatabaseError로 DB 에러 표준화

- 새로운 응답 포맷터 적용:
  * res.created() - 작업보고서 생성 성공 응답
  * res.success() - 기여자별 요약 조회 응답

- 유효성 검사 시스템 적용:
  * validateSchema로 스키마 기반 검증
  * schemas.createDailyWorkReport 스키마 사용

- 함수별 개선사항:
  * createDailyWorkReport: 스키마 검증 및 생성 응답 포맷터
  * getContributorsSummary: Promise 래핑 및 성공 응답 포맷터

- 참고: 파일이 크므로 핵심 함수들만 우선 적용
  추후 나머지 함수들도 점진적으로 리팩토링 예정
2025-11-03 10:54:51 +09:00

549 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// controllers/dailyWorkReportController.js - 권한별 전체 조회 지원 버전
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
const dailyWorkReportService = require('../services/dailyWorkReportService');
const { ApiError, asyncHandler, handleDatabaseError, handleNotFoundError } = require('../utils/errorHandler');
const { validateSchema, schemas } = require('../utils/validator');
/**
* 📝 작업보고서 생성 (V2 - Service Layer 사용)
*/
const createDailyWorkReport = asyncHandler(async (req, res) => {
const reportData = {
...req.body,
created_by: req.user?.user_id || req.user?.id,
created_by_name: req.user?.name || req.user?.username || '알 수 없는 사용자'
};
// 스키마 기반 유효성 검사
validateSchema(reportData, schemas.createDailyWorkReport);
try {
const result = await dailyWorkReportService.createDailyWorkReportService(reportData);
res.created(result, '작업보고서가 성공적으로 생성되었습니다.');
} catch (error) {
console.error('💥 작업보고서 생성 컨트롤러 오류:', error.message);
throw new ApiError('작업보고서 생성에 실패했습니다.', 400);
}
});
/**
* 📊 기여자별 요약 조회 (새로운 기능)
*/
const getContributorsSummary = asyncHandler(async (req, res) => {
const { date, worker_id } = req.query;
if (!date || !worker_id) {
throw new ApiError('date와 worker_id가 필요합니다.', 400);
}
console.log(`📊 기여자별 요약 조회: date=${date}, worker_id=${worker_id}`);
try {
const data = await new Promise((resolve, reject) => {
dailyWorkReportModel.getContributorsByDate(date, worker_id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`);
const result = {
date,
worker_id,
contributors: data,
total_contributors: data.length,
grand_total_hours: totalHours
};
res.success(result, '기여자별 요약 조회 성공');
} catch (err) {
handleDatabaseError(err, '기여자별 요약 조회');
}
});
/**
* 📊 개인 누적 현황 조회 (새로운 기능)
*/
const getMyAccumulatedData = (req, res) => {
const { date, worker_id } = req.query;
const created_by = req.user?.user_id || req.user?.id;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2024-06-16&worker_id=1'
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`📊 개인 누적 현황 조회: date=${date}, worker_id=${worker_id}, created_by=${created_by}`);
dailyWorkReportModel.getMyAccumulatedHours(date, worker_id, created_by, (err, data) => {
if (err) {
console.error('개인 누적 현황 조회 오류:', err);
return res.status(500).json({
error: '개인 누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📊 개인 누적: ${data.my_entry_count}개 항목, ${data.my_total_hours}시간`);
res.json({
date,
worker_id,
created_by,
my_data: data,
timestamp: new Date().toISOString()
});
});
};
/**
* 🗑️ 개별 항목 삭제 (본인 작성분만 - 새로운 기능)
*/
const removeMyEntry = (req, res) => {
const { id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`🗑️ 개별 항목 삭제 요청: id=${id}, 삭제자=${deleted_by}`);
dailyWorkReportModel.removeSpecificEntry(id, deleted_by, (err, result) => {
if (err) {
console.error('개별 항목 삭제 오류:', err);
return res.status(500).json({
error: '항목 삭제 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`✅ 개별 항목 삭제 완료: id=${id}`);
res.json({
message: '항목이 성공적으로 삭제되었습니다.',
id: id,
deleted_by,
timestamp: new Date().toISOString(),
...result
});
});
};
/**
* 📊 작업보고서 조회 (V2 - Service Layer 사용)
*/
const getDailyWorkReports = async (req, res) => {
try {
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
role: req.user?.role || 'user' // 기본값을 'user'로 설정하여 안전하게 처리
};
if (!userInfo.user_id) {
return res.status(401).json({ error: '사용자 인증 정보가 없습니다.' });
}
const reports = await dailyWorkReportService.getDailyWorkReportsService(req.query, userInfo);
res.json(reports);
} catch (error) {
console.error('💥 작업보고서 조회 컨트롤러 오류:', error.message);
res.status(400).json({
success: false,
error: '작업보고서 조회에 실패했습니다.',
details: error.message
});
}
};
/**
* 📊 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
*/
const getDailyWorkReportsByDate = (req, res) => {
const { date } = req.params;
const current_user_id = req.user?.user_id || req.user?.id;
const user_access_level = req.user?.access_level;
if (!current_user_id) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
const isAdmin = user_access_level === 'system' || user_access_level === 'admin';
console.log(`📊 날짜별 조회 (경로): date=${date}, user=${current_user_id}, 권한=${user_access_level}, 관리자=${isAdmin}`);
dailyWorkReportModel.getByDate(date, (err, data) => {
if (err) {
console.error('날짜별 작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
// 🎯 권한별 필터링
let finalData = data;
if (!isAdmin) {
finalData = data.filter(report => report.created_by === current_user_id);
console.log(`📊 권한 필터링: 전체 ${data.length}개 → ${finalData.length}`);
} else {
console.log(`📊 관리자 권한으로 전체 조회: ${data.length}`);
}
res.json(finalData);
});
};
/**
* 🔍 작업보고서 검색 (페이지네이션 포함)
*/
const searchWorkReports = (req, res) => {
const { start_date, end_date, worker_id, project_id, work_status_id, page = 1, limit = 20 } = req.query;
const created_by = req.user?.user_id || req.user?.id;
if (!start_date || !end_date) {
return res.status(400).json({
error: 'start_date와 end_date가 필요합니다.',
example: 'start_date=2024-01-01&end_date=2024-01-31',
optional: ['worker_id', 'project_id', 'work_status_id', 'page', 'limit']
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
const searchParams = {
start_date,
end_date,
worker_id: worker_id ? parseInt(worker_id) : null,
project_id: project_id ? parseInt(project_id) : null,
work_status_id: work_status_id ? parseInt(work_status_id) : null,
created_by, // 작성자 필터링 추가
page: parseInt(page),
limit: parseInt(limit)
};
console.log('🔍 작업보고서 검색 요청:', searchParams);
dailyWorkReportModel.searchWithDetails(searchParams, (err, data) => {
if (err) {
console.error('작업보고서 검색 오류:', err);
return res.status(500).json({
error: '작업보고서 검색 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`🔍 검색 결과: ${data.reports?.length || 0}개 (전체: ${data.total || 0}개)`);
res.json(data);
});
};
/**
* 📈 통계 조회 (V2 - Service Layer 사용)
*/
const getWorkReportStats = async (req, res) => {
try {
const statsData = await dailyWorkReportService.getStatisticsService(req.query);
res.json(statsData);
} catch (error) {
console.error('💥 통계 조회 컨트롤러 오류:', error.message);
res.status(400).json({
success: false,
error: '통계 조회에 실패했습니다.',
details: error.message
});
}
};
/**
* 📊 일일 근무 요약 조회 (V2 - Service Layer 사용)
*/
const getDailySummary = async (req, res) => {
try {
const summaryData = await dailyWorkReportService.getSummaryService(req.query);
res.json(summaryData);
} catch (error) {
console.error('💥 일일 요약 조회 컨트롤러 오류:', error.message);
res.status(400).json({
success: false,
error: '일일 요약 조회에 실패했습니다.',
details: error.message
});
}
};
/**
* 📅 월간 요약 조회
*/
const getMonthlySummary = (req, res) => {
const { year, month } = req.query;
if (!year || !month) {
return res.status(400).json({
error: 'year와 month가 필요합니다.',
example: 'year=2024&month=01',
note: 'month는 01, 02, ..., 12 형식으로 입력하세요.'
});
}
console.log(`📅 월간 요약 조회: ${year}-${month}`);
dailyWorkReportModel.getMonthlySummary(year, month, (err, data) => {
if (err) {
console.error('월간 요약 조회 오류:', err);
return res.status(500).json({
error: '월간 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json({
year: parseInt(year),
month: parseInt(month),
summary: data,
total_entries: data.length,
timestamp: new Date().toISOString()
});
});
};
/**
* ✏️ 작업보고서 수정 (V2 - Service Layer 사용)
*/
const updateWorkReport = async (req, res) => {
try {
const { id: reportId } = req.params;
const updateData = req.body;
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
role: req.user?.role || 'user'
};
if (!userInfo.user_id) {
return res.status(401).json({ error: '사용자 인증 정보가 없습니다.' });
}
const result = await dailyWorkReportService.updateWorkReportService(reportId, updateData, userInfo);
res.json({
success: true,
timestamp: new Date().toISOString(),
...result
});
} catch (error) {
console.error(`💥 작업보고서 수정 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
const statusCode = error.statusCode || 400;
res.status(statusCode).json({
success: false,
error: '작업보고서 수정에 실패했습니다.',
details: error.message
});
}
};
/**
* 🗑️ 특정 작업보고서 삭제 (V2 - Service Layer 사용)
*/
const removeDailyWorkReport = async (req, res) => {
try {
const { id: reportId } = req.params;
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
};
if (!userInfo.user_id) {
return res.status(401).json({ error: '사용자 인증 정보가 없습니다.' });
}
const result = await dailyWorkReportService.removeDailyWorkReportService(reportId, userInfo);
res.json({
success: true,
timestamp: new Date().toISOString(),
...result
});
} catch (error) {
console.error(`💥 작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
const statusCode = error.statusCode || 400;
res.status(statusCode).json({
success: false,
error: '작업보고서 삭제에 실패했습니다.',
details: error.message
});
}
};
/**
* <20><> 작업자의 특정 날짜 전체 삭제
*/
const removeDailyWorkReportByDateAndWorker = (req, res) => {
const { date, worker_id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`🗑️ 날짜+작업자별 전체 삭제 요청: date=${date}, worker_id=${worker_id}, 삭제자=${deleted_by}`);
dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by, (err, affectedRows) => {
if (err) {
console.error('작업보고서 전체 삭제 오류:', err);
return res.status(500).json({
error: '작업보고서 삭제 중 오류가 발생했습니다.',
details: err.message
});
}
if (affectedRows === 0) {
return res.status(404).json({
error: '삭제할 작업보고서를 찾을 수 없습니다.',
date: date,
worker_id: worker_id
});
}
console.log(`✅ 날짜+작업자별 전체 삭제 완료: ${affectedRows}`);
res.json({
message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`,
date,
worker_id,
affected_rows: affectedRows,
deleted_by,
timestamp: new Date().toISOString()
});
});
};
/**
* 📋 마스터 데이터 조회 함수들
*/
const getWorkTypes = (req, res) => {
console.log('📋 작업 유형 조회 요청');
dailyWorkReportModel.getAllWorkTypes((err, data) => {
if (err) {
console.error('작업 유형 조회 오류:', err);
return res.status(500).json({
error: '작업 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 작업 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
const getWorkStatusTypes = (req, res) => {
console.log('📋 업무 상태 유형 조회 요청');
dailyWorkReportModel.getAllWorkStatusTypes((err, data) => {
if (err) {
console.error('업무 상태 유형 조회 오류:', err);
return res.status(500).json({
error: '업무 상태 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 업무 상태 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
const getErrorTypes = (req, res) => {
console.log('📋 에러 유형 조회 요청');
dailyWorkReportModel.getAllErrorTypes((err, data) => {
if (err) {
console.error('에러 유형 조회 오류:', err);
return res.status(500).json({
error: '에러 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 에러 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
/**
* 📊 누적 현황 조회
*/
const getAccumulatedReports = (req, res) => {
const { date, worker_id } = req.query;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2024-06-16&worker_id=1'
});
}
console.log(`📊 누적 현황 조회: date=${date}, worker_id=${worker_id}`);
dailyWorkReportModel.getAccumulatedReportsByDate(date, worker_id, (err, data) => {
if (err) {
console.error('누적 현황 조회 오류:', err);
return res.status(500).json({
error: '누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📊 누적 현황 조회 결과: ${data.length}`);
res.json({
date,
worker_id,
total_entries: data.length,
accumulated_data: data,
timestamp: new Date().toISOString()
});
});
};
// 모든 컨트롤러 함수 내보내기 (리팩토링된 함수 위주로 재구성)
module.exports = {
// 📝 V2 핵심 CRUD 함수
createDailyWorkReport,
getDailyWorkReports,
updateWorkReport,
removeDailyWorkReport,
// 📊 V2 통계 및 요약 함수
getWorkReportStats,
getDailySummary,
// 🔽 아직 리팩토링되지 않은 레거시 함수들
getAccumulatedReports,
getContributorsSummary,
getMyAccumulatedData,
removeMyEntry,
getDailyWorkReportsByDate,
searchWorkReports,
getMonthlySummary,
removeDailyWorkReportByDateAndWorker,
getWorkTypes,
getWorkStatusTypes,
getErrorTypes
};