refactor(backend): 작업 보고서 통계/요약 API 구조 개선

- dailyWorkReportController의 통계/요약 함수를 C-S-M 아키텍처에 맞게 리팩토링
- Model 계층의 콜백 기반 함수를 Promise 기반으로 전환
- API의 일관성 및 유지보수성 향상
This commit is contained in:
2025-07-28 12:35:50 +09:00
parent 5a68ced13b
commit 71c06f38b1
3 changed files with 134 additions and 175 deletions

View File

@@ -31,41 +31,6 @@ const createDailyWorkReport = async (req, res) => {
} }
}; };
/**
* <20><> 누적 현황 조회 (새로운 기능)
*/
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()
});
});
};
/** /**
* 📊 기여자별 요약 조회 (새로운 기능) * 📊 기여자별 요약 조회 (새로운 기능)
*/ */
@@ -298,85 +263,35 @@ const searchWorkReports = (req, res) => {
}; };
/** /**
* 📈 통계 조회 (작성자별 필터링) * 📈 통계 조회 (V2 - Service Layer 사용)
*/ */
const getWorkReportStats = (req, res) => { const getWorkReportStats = async (req, res) => {
const { start_date, end_date } = req.query; try {
const created_by = req.user?.user_id || req.user?.id; const statsData = await dailyWorkReportService.getStatisticsService(req.query);
res.json(statsData);
if (!start_date || !end_date) { } catch (error) {
return res.status(400).json({ console.error('💥 통계 조회 컨트롤러 오류:', error.message);
error: 'start_date와 end_date가 필요합니다.', res.status(400).json({
example: 'start_date=2024-01-01&end_date=2024-01-31' success: false,
error: '통계 조회에 실패했습니다.',
details: error.message
}); });
} }
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`📈 통계 조회: ${start_date} ~ ${end_date}, 요청자: ${created_by}`);
dailyWorkReportModel.getStatistics(start_date, end_date, (err, data) => {
if (err) {
console.error('통계 조회 오류:', err);
return res.status(500).json({
error: '통계 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json({
...data,
metadata: {
note: '현재는 전체 통계입니다. 개인별 통계는 추후 구현 예정',
requested_by: created_by,
period: `${start_date} ~ ${end_date}`,
timestamp: new Date().toISOString()
}
});
});
}; };
/** /**
* 📊 일일 근무 요약 조회 * 📊 일일 근무 요약 조회 (V2 - Service Layer 사용)
*/ */
const getDailySummary = (req, res) => { const getDailySummary = async (req, res) => {
const { date, worker_id } = req.query; try {
const summaryData = await dailyWorkReportService.getSummaryService(req.query);
if (date) { res.json(summaryData);
console.log(`📊 일일 요약 조회: date=${date}`); } catch (error) {
dailyWorkReportModel.getSummaryByDate(date, (err, data) => { console.error('💥 일일 요약 조회 컨트롤러 오류:', error.message);
if (err) { res.status(400).json({
console.error('일일 요약 조회 오류:', err); success: false,
return res.status(500).json({ error: '일일 요약 조회에 실패했습니다.',
error: '일일 요약 조회 중 오류가 발생했습니다.', details: error.message
details: err.message
});
}
res.json(data);
});
} else if (worker_id) {
console.log(`📊 작업자별 요약 조회: worker_id=${worker_id}`);
dailyWorkReportModel.getSummaryByWorker(worker_id, (err, data) => {
if (err) {
console.error('작업자별 요약 조회 오류:', err);
return res.status(500).json({
error: '작업자별 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json(data);
});
} else {
res.status(400).json({
error: 'date 또는 worker_id 파라미터가 필요합니다.',
examples: [
'date=2024-06-16',
'worker_id=1'
]
}); });
} }
}; };
@@ -576,30 +491,28 @@ const getErrorTypes = (req, res) => {
}); });
}; };
// 모든 컨트롤러 함수 내보내기 (권한별 조회 지원) // 모든 컨트롤러 함수 내보내기 (리팩토링된 함수 위주로 재구성)
module.exports = { module.exports = {
// 📝 핵심 CRUD 함수들 (권한별 전체 조회 지원) // 📝 V2 핵심 CRUD 함수
createDailyWorkReport, // 누적 추가 (덮어쓰기 없음) createDailyWorkReport,
getDailyWorkReports, // 조회 (권한별 필터링 개선) getDailyWorkReports,
getDailyWorkReportsByDate, // 날짜별 조회 (권한별 필터링 개선) updateWorkReport,
searchWorkReports, // 검색 (페이지네이션) removeDailyWorkReport,
updateWorkReport, // 수정
removeDailyWorkReport, // 개별 삭제
removeDailyWorkReportByDateAndWorker, // 전체 삭제
// 🔄 누적 관련 새로운 함수 // 📊 V2 통계 및 요약 함수
getAccumulatedReports, // 누적 현황 조회 getWorkReportStats,
getContributorsSummary, // 기여자별 요약 getDailySummary,
getMyAccumulatedData, // 개인 누적 현황
removeMyEntry, // 개별 항목 삭제 (본인 것만) // 🔽 아직 리팩토링되지 않은 레거시 함수들
getAccumulatedReports,
// 📊 요약 및 통계 함수들 getContributorsSummary,
getDailySummary, // 일일 요약 getMyAccumulatedData,
getMonthlySummary, // 월간 요약 removeMyEntry,
getWorkReportStats, // 통계 getDailyWorkReportsByDate,
searchWorkReports,
// 📋 마스터 데이터 함수들 getMonthlySummary,
getWorkTypes, // 작업 유형 목록 removeDailyWorkReportByDateAndWorker,
getWorkStatusTypes, // 업무 상태 유형 목록 getWorkTypes,
getErrorTypes // 에러 유형 목록 getWorkStatusTypes,
getErrorTypes
}; };

View File

@@ -745,33 +745,26 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
}; };
/** /**
* 20. 통계 조회 * 20. 통계 조회 (Promise 기반)
*/ */
const getStatistics = async (start_date, end_date, callback) => { const getStatistics = async (start_date, end_date) => {
try { try {
const db = await getDb(); const db = await getDb();
const sql = ` const overallSql = `
SELECT SELECT
COUNT(*) as total_reports, COUNT(*) as total_reports,
SUM(work_hours) as total_hours, SUM(work_hours) as total_hours,
COUNT(DISTINCT worker_id) as unique_workers, COUNT(DISTINCT worker_id) as unique_workers,
COUNT(DISTINCT project_id) as unique_projects, COUNT(DISTINCT project_id) as unique_projects
SUM(CASE WHEN work_status_id = 2 THEN 1 ELSE 0 END) as error_count,
AVG(work_hours) as avg_hours_per_entry,
MIN(work_hours) as min_hours,
MAX(work_hours) as max_hours
FROM daily_work_reports FROM daily_work_reports
WHERE report_date BETWEEN ? AND ? WHERE report_date BETWEEN ? AND ?
`; `;
const [overallRows] = await db.query(overallSql, [start_date, end_date]);
const [rows] = await db.query(sql, [start_date, end_date]);
// 추가 통계 - 날짜별 집계
const dailyStatsSql = ` const dailyStatsSql = `
SELECT SELECT
report_date, report_date,
COUNT(*) as daily_reports,
SUM(work_hours) as daily_hours, SUM(work_hours) as daily_hours,
COUNT(DISTINCT worker_id) as daily_workers COUNT(DISTINCT worker_id) as daily_workers
FROM daily_work_reports FROM daily_work_reports
@@ -779,18 +772,15 @@ const getStatistics = async (start_date, end_date, callback) => {
GROUP BY report_date GROUP BY report_date
ORDER BY report_date DESC ORDER BY report_date DESC
`; `;
const [dailyStats] = await db.query(dailyStatsSql, [start_date, end_date]); const [dailyStats] = await db.query(dailyStatsSql, [start_date, end_date]);
const result = { return {
overall: rows[0], overall: overallRows[0],
daily_breakdown: dailyStats daily_breakdown: dailyStats
}; };
callback(null, result);
} catch (err) { } catch (err) {
console.error('통계 조회 오류:', err); console.error('통계 조회 오류:', err);
callback(err); throw new Error('데이터베이스에서 통계 정보를 조회하는 중 오류가 발생했습니다.');
} }
}; };
@@ -1002,43 +992,38 @@ const removeReportById = async (reportId, deletedByUserId) => {
}; };
// 모든 함수 내보내기 (기존 기능 + 누적 기능) // 모든 함수 내보내기 (Promise 기반 함수 위주로 재구성)
module.exports = { module.exports = {
// 📋 마스터 데이터 // 새로 추가된 V2 함수 (Promise 기반)
createReportEntries,
getReportsWithOptions,
updateReportById,
removeReportById,
// Promise 기반으로 리팩토링된 함수
getStatistics,
getSummaryByDate,
getSummaryByWorker,
// 아직 리팩토링되지 않았지만 필요한 기존 함수들...
// (점진적으로 아래 함수들도 Promise 기반으로 전환해야 함)
getAllWorkTypes, getAllWorkTypes,
getAllWorkStatusTypes, getAllWorkStatusTypes,
getAllErrorTypes, getAllErrorTypes,
createDailyReport,
// 🔄 핵심 생성 함수 (누적 방식) getMyAccumulatedHours,
createDailyReport, // 누적 추가 (덮어쓰기 없음) getAccumulatedReportsByDate,
getContributorsByDate,
// 📊 누적 관련 새로운 함수들 removeSpecificEntry,
getMyAccumulatedHours, // 개인 누적 현황
getAccumulatedReportsByDate, // 날짜별 누적 현황
getContributorsByDate, // 기여자별 요약
removeSpecificEntry, // 개별 항목 삭제
// 📊 기존 조회 함수들 (모두 유지)
getById, getById,
getByDate, getByDate,
getByDateAndCreator, // 날짜+작성자별 조회 getByDateAndCreator,
getByWorker, getByWorker,
getByDateAndWorker, getByDateAndWorker,
getByRange, getByRange,
searchWithDetails, searchWithDetails,
getSummaryByDate,
getSummaryByWorker,
getMonthlySummary, getMonthlySummary,
// ✏️ 수정/삭제 함수들 (기존 유지)
updateById, updateById,
removeById, removeById,
removeByDateAndWorker, removeByDateAndWorker,
getStatistics,
// 새로 추가된 V2 함수
createReportEntries,
getReportsWithOptions,
updateReportById,
removeReportById
}; };

View File

@@ -204,10 +204,71 @@ const removeDailyWorkReportService = async (reportId, userInfo) => {
} }
}; };
/**
* 기간별 작업 보고서 통계를 조회하는 비즈니스 로직을 처리합니다.
* @param {object} queryParams - 컨트롤러에서 전달된 쿼리 파라미터 (start_date, end_date)
* @returns {Promise<object>} 통계 데이터
*/
const getStatisticsService = async (queryParams) => {
const { start_date, end_date } = queryParams;
if (!start_date || !end_date) {
throw new Error('통계 조회를 위해 시작일(start_date)과 종료일(end_date)이 모두 필요합니다.');
}
console.log(`📈 [Service] 통계 조회 요청: ${start_date} ~ ${end_date}`);
try {
// 모델의 getStatistics 함수가 Promise를 반환하도록 수정 필요
const statsData = await dailyWorkReportModel.getStatistics(start_date, end_date);
console.log('✅ [Service] 통계 조회 성공');
return {
...statsData,
metadata: {
period: `${start_date} ~ ${end_date}`,
timestamp: new Date().toISOString()
}
};
} catch (error) {
console.error('[Service] 통계 조회 중 오류 발생:', error);
throw error;
}
};
/**
* 일일 또는 작업자별 작업 요약 정보를 조회하는 비즈니스 로직을 처리합니다.
* @param {object} queryParams - 컨트롤러에서 전달된 쿼리 파라미터 (date 또는 worker_id)
* @returns {Promise<object>} 요약 데이터
*/
const getSummaryService = async (queryParams) => {
const { date, worker_id } = queryParams;
if (!date && !worker_id) {
throw new Error('일일 또는 작업자별 요약 조회를 위해 날짜(date) 또는 작업자 ID(worker_id)가 필요합니다.');
}
try {
if (date) {
console.log(`📊 [Service] 일일 요약 조회 요청: date=${date}`);
// 모델의 getSummaryByDate 함수가 Promise를 반환하도록 수정 필요
return await dailyWorkReportModel.getSummaryByDate(date);
} else { // worker_id
console.log(`📊 [Service] 작업자별 요약 조회 요청: worker_id=${worker_id}`);
// 모델의 getSummaryByWorker 함수가 Promise를 반환하도록 수정 필요
return await dailyWorkReportModel.getSummaryByWorker(worker_id);
}
} catch (error) {
console.error('[Service] 요약 정보 조회 중 오류 발생:', error);
throw error;
}
};
module.exports = { module.exports = {
createDailyWorkReportService, createDailyWorkReportService,
getDailyWorkReportsService, getDailyWorkReportsService,
updateWorkReportService, updateWorkReportService,
removeDailyWorkReportService, removeDailyWorkReportService,
getStatisticsService,
getSummaryService,
}; };