feat: Phase 3.8 - 복잡한 분석 컨트롤러 개선
두 개의 복잡한 분석 컨트롤러를 현대적인 패턴으로 전면 개선: ## workReportAnalysisController.js (381 → 430 lines) - 7개 SQL 쿼리 기반 복합 분석 엔드포인트 개선 - console.error → logger.info/error/warn 전환 - try-catch → asyncHandler 미들웨어 적용 - Error → ValidationError, DatabaseError 전환 - JSDoc 문서화 및 구조화된 로깅 추가 - 4개 함수: getAnalysisFilters, getAnalyticsByPeriod, getProjectAnalysis, getWorkerAnalysis ## workAnalysisController.js (523 → 622 lines) - 클래스 기반 → 함수 기반 컨트롤러 전환 - console.error → logger.info/error/debug 전환 - try-catch → asyncHandler 미들웨어 적용 - Error → ValidationError, DatabaseError 전환 - validateDateRange 헬퍼 함수 개선 (상세한 에러 컨텍스트) - JSDoc 문서화 및 구조화된 로깅 추가 - 12개 함수: getStats, getDailyTrend, getWorkerStats, getProjectStats, getWorkTypeStats, getRecentWork, getWeekdayPattern, getErrorAnalysis, getMonthlyComparison, getWorkerSpecialization, getDashboardData, getProjectWorkTypeAnalysis ## 기술적 개선사항 - 통합 에러 처리: 커스텀 에러 클래스로 일관된 에러 핸들링 - 구조화된 로깅: 모든 API 호출에 컨텍스트 정보 포함 - 자동 에러 전파: asyncHandler로 보일러플레이트 코드 제거 - 향상된 유효성 검사: 상세한 에러 메시지와 컨텍스트 - 프로덕션 준비: 표준화된 응답 형식 및 에러 처리 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,64 @@
|
|||||||
// controllers/workReportAnalysisController.js - 데일리 워크 레포트 분석 전용 컨트롤러
|
/**
|
||||||
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
|
* 데일리 워크 레포트 분석 컨트롤러
|
||||||
|
*
|
||||||
|
* 작업 보고서 종합 분석 API 엔드포인트 핸들러
|
||||||
|
*
|
||||||
|
* @author TK-FB-Project
|
||||||
|
* @since 2025-12-11
|
||||||
|
*/
|
||||||
|
|
||||||
const { getDb } = require('../dbPool');
|
const { getDb } = require('../dbPool');
|
||||||
|
const { ValidationError, DatabaseError } = require('../utils/errors');
|
||||||
|
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 📋 분석용 필터 데이터 조회 (프로젝트, 작업자, 작업유형 목록)
|
* 분석용 필터 데이터 조회 (프로젝트, 작업자, 작업유형 목록)
|
||||||
*/
|
*/
|
||||||
const getAnalysisFilters = async (req, res) => {
|
const getAnalysisFilters = asyncHandler(async (req, res) => {
|
||||||
|
logger.info('분석 필터 데이터 조회 요청');
|
||||||
|
|
||||||
|
const db = await getDb();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
|
||||||
|
|
||||||
// 프로젝트 목록
|
// 프로젝트 목록
|
||||||
const [projects] = await db.query(`
|
const [projects] = await db.query(`
|
||||||
SELECT DISTINCT p.project_id, p.project_name
|
SELECT DISTINCT p.project_id, p.project_name
|
||||||
FROM projects p
|
FROM projects p
|
||||||
INNER JOIN daily_work_reports dwr ON p.project_id = dwr.project_id
|
INNER JOIN daily_work_reports dwr ON p.project_id = dwr.project_id
|
||||||
ORDER BY p.project_name
|
ORDER BY p.project_name
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// 작업자 목록
|
// 작업자 목록
|
||||||
const [workers] = await db.query(`
|
const [workers] = await db.query(`
|
||||||
SELECT DISTINCT w.worker_id, w.worker_name
|
SELECT DISTINCT w.worker_id, w.worker_name
|
||||||
FROM workers w
|
FROM workers w
|
||||||
INNER JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id
|
INNER JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id
|
||||||
ORDER BY w.worker_name
|
ORDER BY w.worker_name
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// 작업 유형 목록
|
// 작업 유형 목록
|
||||||
const [workTypes] = await db.query(`
|
const [workTypes] = await db.query(`
|
||||||
SELECT DISTINCT wt.id as work_type_id, wt.name as work_type_name
|
SELECT DISTINCT wt.id as work_type_id, wt.name as work_type_name
|
||||||
FROM work_types wt
|
FROM work_types wt
|
||||||
INNER JOIN daily_work_reports dwr ON wt.id = dwr.work_type_id
|
INNER JOIN daily_work_reports dwr ON wt.id = dwr.work_type_id
|
||||||
ORDER BY wt.name
|
ORDER BY wt.name
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// 날짜 범위 (최초/최신 데이터)
|
// 날짜 범위
|
||||||
const [dateRange] = await db.query(`
|
const [dateRange] = await db.query(`
|
||||||
SELECT
|
SELECT
|
||||||
MIN(report_date) as min_date,
|
MIN(report_date) as min_date,
|
||||||
MAX(report_date) as max_date
|
MAX(report_date) as max_date
|
||||||
FROM daily_work_reports
|
FROM daily_work_reports
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
logger.info('분석 필터 데이터 조회 성공', {
|
||||||
|
projects: projects.length,
|
||||||
|
workers: workers.length,
|
||||||
|
workTypes: workTypes.length
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@@ -48,57 +66,58 @@ const getAnalysisFilters = async (req, res) => {
|
|||||||
workers,
|
workers,
|
||||||
workTypes,
|
workTypes,
|
||||||
dateRange: dateRange[0]
|
dateRange: dateRange[0]
|
||||||
}
|
},
|
||||||
|
message: '분석 필터 데이터 조회 성공'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('필터 데이터 조회 오류:', error);
|
logger.error('분석 필터 데이터 조회 실패', { error: error.message });
|
||||||
res.status(500).json({
|
throw new DatabaseError('필터 데이터 조회 중 오류가 발생했습니다');
|
||||||
success: false,
|
|
||||||
error: '필터 데이터 조회 중 오류가 발생했습니다.',
|
|
||||||
detail: error.message
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 📊 기간별 작업 분석 데이터 조회
|
* 기간별 작업 분석 데이터 조회
|
||||||
*/
|
*/
|
||||||
const getAnalyticsByPeriod = async (req, res) => {
|
const getAnalyticsByPeriod = asyncHandler(async (req, res) => {
|
||||||
try {
|
const { start_date, end_date, project_id, worker_id } = req.query;
|
||||||
const { start_date, end_date, project_id, worker_id } = req.query;
|
|
||||||
|
|
||||||
if (!start_date || !end_date) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'start_date와 end_date가 필요합니다.',
|
|
||||||
example: 'start_date=2025-08-01&end_date=2025-08-31'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await getDb();
|
if (!start_date || !end_date) {
|
||||||
|
throw new ValidationError('start_date와 end_date가 필요합니다', {
|
||||||
|
required: ['start_date', 'end_date'],
|
||||||
|
received: { start_date, end_date },
|
||||||
|
example: 'start_date=2025-08-01&end_date=2025-08-31'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('기간별 분석 데이터 조회 요청', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
project_id,
|
||||||
|
worker_id
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = await getDb();
|
||||||
|
|
||||||
|
try {
|
||||||
// 기본 조건
|
// 기본 조건
|
||||||
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
||||||
let queryParams = [start_date, end_date];
|
let queryParams = [start_date, end_date];
|
||||||
|
|
||||||
// 프로젝트 필터
|
|
||||||
if (project_id) {
|
if (project_id) {
|
||||||
whereConditions.push('dwr.project_id = ?');
|
whereConditions.push('dwr.project_id = ?');
|
||||||
queryParams.push(project_id);
|
queryParams.push(project_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 작업자 필터
|
|
||||||
if (worker_id) {
|
if (worker_id) {
|
||||||
whereConditions.push('dwr.worker_id = ?');
|
whereConditions.push('dwr.worker_id = ?');
|
||||||
queryParams.push(worker_id);
|
queryParams.push(worker_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = whereConditions.join(' AND ');
|
const whereClause = whereConditions.join(' AND ');
|
||||||
|
|
||||||
// 1. 전체 요약 통계 (에러 분석 포함)
|
// 1. 전체 요약 통계
|
||||||
const overallSql = `
|
const overallSql = `
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) as total_entries,
|
COUNT(*) as total_entries,
|
||||||
SUM(dwr.work_hours) as total_hours,
|
SUM(dwr.work_hours) as total_hours,
|
||||||
COUNT(DISTINCT dwr.worker_id) as unique_workers,
|
COUNT(DISTINCT dwr.worker_id) as unique_workers,
|
||||||
@@ -111,12 +130,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
FROM daily_work_reports dwr
|
FROM daily_work_reports dwr
|
||||||
WHERE ${whereClause}
|
WHERE ${whereClause}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [overallStats] = await db.query(overallSql, queryParams);
|
const [overallStats] = await db.query(overallSql, queryParams);
|
||||||
|
|
||||||
// 2. 일별 통계
|
// 2. 일별 통계
|
||||||
const dailyStatsSql = `
|
const dailyStatsSql = `
|
||||||
SELECT
|
SELECT
|
||||||
dwr.report_date,
|
dwr.report_date,
|
||||||
SUM(dwr.work_hours) as daily_hours,
|
SUM(dwr.work_hours) as daily_hours,
|
||||||
COUNT(*) as daily_entries,
|
COUNT(*) as daily_entries,
|
||||||
@@ -126,12 +145,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY dwr.report_date
|
GROUP BY dwr.report_date
|
||||||
ORDER BY dwr.report_date ASC
|
ORDER BY dwr.report_date ASC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [dailyStats] = await db.query(dailyStatsSql, queryParams);
|
const [dailyStats] = await db.query(dailyStatsSql, queryParams);
|
||||||
|
|
||||||
// 2.5. 일별 에러 발생 통계
|
// 3. 일별 에러 통계
|
||||||
const dailyErrorStatsSql = `
|
const dailyErrorStatsSql = `
|
||||||
SELECT
|
SELECT
|
||||||
dwr.report_date,
|
dwr.report_date,
|
||||||
COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as daily_errors,
|
COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as daily_errors,
|
||||||
COUNT(*) as daily_total,
|
COUNT(*) as daily_total,
|
||||||
@@ -141,12 +160,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY dwr.report_date
|
GROUP BY dwr.report_date
|
||||||
ORDER BY dwr.report_date ASC
|
ORDER BY dwr.report_date ASC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [dailyErrorStats] = await db.query(dailyErrorStatsSql, queryParams);
|
const [dailyErrorStats] = await db.query(dailyErrorStatsSql, queryParams);
|
||||||
|
|
||||||
// 3. 에러 유형별 분석 (간단한 방식으로 수정)
|
// 4. 에러 유형별 분석
|
||||||
const errorAnalysisSql = `
|
const errorAnalysisSql = `
|
||||||
SELECT
|
SELECT
|
||||||
et.id as error_type_id,
|
et.id as error_type_id,
|
||||||
et.name as error_type_name,
|
et.name as error_type_name,
|
||||||
COUNT(*) as error_count,
|
COUNT(*) as error_count,
|
||||||
@@ -158,12 +177,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY et.id, et.name
|
GROUP BY et.id, et.name
|
||||||
ORDER BY error_count DESC
|
ORDER BY error_count DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [errorAnalysis] = await db.query(errorAnalysisSql, queryParams);
|
const [errorAnalysis] = await db.query(errorAnalysisSql, queryParams);
|
||||||
|
|
||||||
// 4. 작업 유형별 분석
|
// 5. 작업 유형별 분석
|
||||||
const workTypeAnalysisSql = `
|
const workTypeAnalysisSql = `
|
||||||
SELECT
|
SELECT
|
||||||
wt.id as work_type_id,
|
wt.id as work_type_id,
|
||||||
wt.name as work_type_name,
|
wt.name as work_type_name,
|
||||||
COUNT(*) as work_count,
|
COUNT(*) as work_count,
|
||||||
@@ -177,12 +196,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY wt.id, wt.name
|
GROUP BY wt.id, wt.name
|
||||||
ORDER BY total_hours DESC
|
ORDER BY total_hours DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [workTypeAnalysis] = await db.query(workTypeAnalysisSql, queryParams);
|
const [workTypeAnalysis] = await db.query(workTypeAnalysisSql, queryParams);
|
||||||
|
|
||||||
// 5. 작업자별 성과 분석
|
// 6. 작업자별 성과 분석
|
||||||
const workerAnalysisSql = `
|
const workerAnalysisSql = `
|
||||||
SELECT
|
SELECT
|
||||||
w.worker_id,
|
w.worker_id,
|
||||||
w.worker_name,
|
w.worker_name,
|
||||||
COUNT(*) as total_entries,
|
COUNT(*) as total_entries,
|
||||||
@@ -198,12 +217,12 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY w.worker_id, w.worker_name
|
GROUP BY w.worker_id, w.worker_name
|
||||||
ORDER BY total_hours DESC
|
ORDER BY total_hours DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [workerAnalysis] = await db.query(workerAnalysisSql, queryParams);
|
const [workerAnalysis] = await db.query(workerAnalysisSql, queryParams);
|
||||||
|
|
||||||
// 6. 프로젝트별 분석
|
// 7. 프로젝트별 분석
|
||||||
const projectAnalysisSql = `
|
const projectAnalysisSql = `
|
||||||
SELECT
|
SELECT
|
||||||
p.project_id,
|
p.project_id,
|
||||||
p.project_name,
|
p.project_name,
|
||||||
COUNT(*) as total_entries,
|
COUNT(*) as total_entries,
|
||||||
@@ -219,9 +238,16 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
GROUP BY p.project_id, p.project_name
|
GROUP BY p.project_id, p.project_name
|
||||||
ORDER BY total_hours DESC
|
ORDER BY total_hours DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [projectAnalysis] = await db.query(projectAnalysisSql, queryParams);
|
const [projectAnalysis] = await db.query(projectAnalysisSql, queryParams);
|
||||||
|
|
||||||
|
logger.info('기간별 분석 데이터 조회 성공', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
total_entries: overallStats[0].total_entries,
|
||||||
|
total_hours: overallStats[0].total_hours
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@@ -234,48 +260,53 @@ const getAnalyticsByPeriod = async (req, res) => {
|
|||||||
projectAnalysis,
|
projectAnalysis,
|
||||||
period: { start_date, end_date },
|
period: { start_date, end_date },
|
||||||
filters: { project_id, worker_id }
|
filters: { project_id, worker_id }
|
||||||
}
|
},
|
||||||
|
message: '기간별 분석 데이터 조회 성공'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('기간별 분석 데이터 조회 오류:', error);
|
logger.error('기간별 분석 데이터 조회 실패', {
|
||||||
res.status(500).json({
|
start_date,
|
||||||
success: false,
|
end_date,
|
||||||
error: '기간별 분석 데이터 조회 중 오류가 발생했습니다.',
|
error: error.message
|
||||||
detail: error.message
|
|
||||||
});
|
});
|
||||||
|
throw new DatabaseError('기간별 분석 데이터 조회 중 오류가 발생했습니다');
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 📈 프로젝트별 상세 분석
|
* 프로젝트별 상세 분석
|
||||||
*/
|
*/
|
||||||
const getProjectAnalysis = async (req, res) => {
|
const getProjectAnalysis = asyncHandler(async (req, res) => {
|
||||||
try {
|
const { start_date, end_date, project_id } = req.query;
|
||||||
const { start_date, end_date, project_id } = req.query;
|
|
||||||
|
|
||||||
if (!start_date || !end_date) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'start_date와 end_date가 필요합니다.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await getDb();
|
if (!start_date || !end_date) {
|
||||||
|
throw new ValidationError('start_date와 end_date가 필요합니다', {
|
||||||
|
required: ['start_date', 'end_date'],
|
||||||
|
received: { start_date, end_date }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('프로젝트별 분석 조회 요청', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
project_id
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = await getDb();
|
||||||
|
|
||||||
|
try {
|
||||||
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
||||||
let queryParams = [start_date, end_date];
|
let queryParams = [start_date, end_date];
|
||||||
|
|
||||||
if (project_id) {
|
if (project_id) {
|
||||||
whereConditions.push('dwr.project_id = ?');
|
whereConditions.push('dwr.project_id = ?');
|
||||||
queryParams.push(project_id);
|
queryParams.push(project_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = whereConditions.join(' AND ');
|
const whereClause = whereConditions.join(' AND ');
|
||||||
|
|
||||||
// 프로젝트별 통계
|
|
||||||
const projectStatsSql = `
|
const projectStatsSql = `
|
||||||
SELECT
|
SELECT
|
||||||
dwr.project_id,
|
dwr.project_id,
|
||||||
p.project_name,
|
p.project_name,
|
||||||
SUM(dwr.work_hours) as total_hours,
|
SUM(dwr.work_hours) as total_hours,
|
||||||
@@ -289,56 +320,67 @@ const getProjectAnalysis = async (req, res) => {
|
|||||||
GROUP BY dwr.project_id
|
GROUP BY dwr.project_id
|
||||||
ORDER BY total_hours DESC
|
ORDER BY total_hours DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [projectStats] = await db.query(projectStatsSql, queryParams);
|
const [projectStats] = await db.query(projectStatsSql, queryParams);
|
||||||
|
|
||||||
|
logger.info('프로젝트별 분석 조회 성공', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
projectCount: projectStats.length
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
projectStats,
|
projectStats,
|
||||||
period: { start_date, end_date }
|
period: { start_date, end_date }
|
||||||
}
|
},
|
||||||
|
message: '프로젝트별 분석 조회 성공'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('프로젝트별 분석 데이터 조회 오류:', error);
|
logger.error('프로젝트별 분석 조회 실패', {
|
||||||
res.status(500).json({
|
start_date,
|
||||||
success: false,
|
end_date,
|
||||||
error: '프로젝트별 분석 데이터 조회 중 오류가 발생했습니다.',
|
error: error.message
|
||||||
detail: error.message
|
|
||||||
});
|
});
|
||||||
|
throw new DatabaseError('프로젝트별 분석 데이터 조회 중 오류가 발생했습니다');
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 👤 작업자별 상세 분석
|
* 작업자별 상세 분석
|
||||||
*/
|
*/
|
||||||
const getWorkerAnalysis = async (req, res) => {
|
const getWorkerAnalysis = asyncHandler(async (req, res) => {
|
||||||
try {
|
const { start_date, end_date, worker_id } = req.query;
|
||||||
const { start_date, end_date, worker_id } = req.query;
|
|
||||||
|
|
||||||
if (!start_date || !end_date) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'start_date와 end_date가 필요합니다.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await getDb();
|
if (!start_date || !end_date) {
|
||||||
|
throw new ValidationError('start_date와 end_date가 필요합니다', {
|
||||||
|
required: ['start_date', 'end_date'],
|
||||||
|
received: { start_date, end_date }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('작업자별 분석 조회 요청', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
worker_id
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = await getDb();
|
||||||
|
|
||||||
|
try {
|
||||||
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
let whereConditions = ['dwr.report_date BETWEEN ? AND ?'];
|
||||||
let queryParams = [start_date, end_date];
|
let queryParams = [start_date, end_date];
|
||||||
|
|
||||||
if (worker_id) {
|
if (worker_id) {
|
||||||
whereConditions.push('dwr.worker_id = ?');
|
whereConditions.push('dwr.worker_id = ?');
|
||||||
queryParams.push(worker_id);
|
queryParams.push(worker_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = whereConditions.join(' AND ');
|
const whereClause = whereConditions.join(' AND ');
|
||||||
|
|
||||||
// 작업자별 통계
|
|
||||||
const workerStatsSql = `
|
const workerStatsSql = `
|
||||||
SELECT
|
SELECT
|
||||||
dwr.worker_id,
|
dwr.worker_id,
|
||||||
w.worker_name,
|
w.worker_name,
|
||||||
SUM(dwr.work_hours) as total_hours,
|
SUM(dwr.work_hours) as total_hours,
|
||||||
@@ -352,30 +394,36 @@ const getWorkerAnalysis = async (req, res) => {
|
|||||||
GROUP BY dwr.worker_id
|
GROUP BY dwr.worker_id
|
||||||
ORDER BY total_hours DESC
|
ORDER BY total_hours DESC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [workerStats] = await db.query(workerStatsSql, queryParams);
|
const [workerStats] = await db.query(workerStatsSql, queryParams);
|
||||||
|
|
||||||
|
logger.info('작업자별 분석 조회 성공', {
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
workerCount: workerStats.length
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
workerStats,
|
workerStats,
|
||||||
period: { start_date, end_date }
|
period: { start_date, end_date }
|
||||||
}
|
},
|
||||||
|
message: '작업자별 분석 조회 성공'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('작업자별 분석 데이터 조회 오류:', error);
|
logger.error('작업자별 분석 조회 실패', {
|
||||||
res.status(500).json({
|
start_date,
|
||||||
success: false,
|
end_date,
|
||||||
error: '작업자별 분석 데이터 조회 중 오류가 발생했습니다.',
|
error: error.message
|
||||||
detail: error.message
|
|
||||||
});
|
});
|
||||||
|
throw new DatabaseError('작업자별 분석 데이터 조회 중 오류가 발생했습니다');
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAnalysisFilters,
|
getAnalysisFilters,
|
||||||
getAnalyticsByPeriod,
|
getAnalyticsByPeriod,
|
||||||
getProjectAnalysis,
|
getProjectAnalysis,
|
||||||
getWorkerAnalysis
|
getWorkerAnalysis
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user