Files
TK-FB-Project/api.hyungi.net/controllers/dailyWorkReportController.js
hyungi d154514fa0 refactor: 일일 작업 보고서 CRUD API 구조 개선
- dailyWorkReportController의 조회, 수정, 삭제(CRUD) 로직을 Service와 Model로 분리
 - Promise 기반의 async/await 구조로 비동기 코드 개선
 - 새로운 DB 스키마 v2의 명명 규칙 적용
2025-07-28 11:19:28 +09:00

605 lines
18 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');
/**
* 📝 작업보고서 생성 (V2 - Service Layer 사용)
*/
const createDailyWorkReport = async (req, res) => {
try {
const reportData = {
...req.body,
created_by: req.user?.user_id || req.user?.id,
created_by_name: req.user?.name || req.user?.username || '알 수 없는 사용자'
};
const result = await dailyWorkReportService.createDailyWorkReportService(reportData);
res.status(201).json({
success: true,
timestamp: new Date().toISOString(),
...result
});
} catch (error) {
console.error('💥 작업보고서 생성 컨트롤러 오류:', error.message);
res.status(400).json({
success: false,
error: '작업보고서 생성에 실패했습니다.',
details: error.message
});
}
};
/**
* <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()
});
});
};
/**
* 📊 기여자별 요약 조회 (새로운 기능)
*/
const getContributorsSummary = (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.getContributorsByDate(date, worker_id, (err, data) => {
if (err) {
console.error('기여자별 요약 조회 오류:', err);
return res.status(500).json({
error: '기여자별 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`);
res.json({
date,
worker_id,
contributors: data,
total_contributors: data.length,
grand_total_hours: totalHours,
timestamp: new Date().toISOString()
});
});
};
/**
* 📊 개인 누적 현황 조회 (새로운 기능)
*/
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);
});
};
/**
* 📈 통계 조회 (작성자별 필터링)
*/
const getWorkReportStats = (req, res) => {
const { start_date, end_date } = 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'
});
}
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()
}
});
});
};
/**
* 📊 일일 근무 요약 조회
*/
const getDailySummary = (req, res) => {
const { date, worker_id } = req.query;
if (date) {
console.log(`📊 일일 요약 조회: date=${date}`);
dailyWorkReportModel.getSummaryByDate(date, (err, data) => {
if (err) {
console.error('일일 요약 조회 오류:', err);
return res.status(500).json({
error: '일일 요약 조회 중 오류가 발생했습니다.',
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'
]
});
}
};
/**
* 📅 월간 요약 조회
*/
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);
});
};
// 모든 컨트롤러 함수 내보내기 (권한별 조회 지원)
module.exports = {
// 📝 핵심 CRUD 함수들 (권한별 전체 조회 지원)
createDailyWorkReport, // 누적 추가 (덮어쓰기 없음)
getDailyWorkReports, // 조회 (권한별 필터링 개선)
getDailyWorkReportsByDate, // 날짜별 조회 (권한별 필터링 개선)
searchWorkReports, // 검색 (페이지네이션)
updateWorkReport, // 수정
removeDailyWorkReport, // 개별 삭제
removeDailyWorkReportByDateAndWorker, // 전체 삭제
// 🔄 누적 관련 새로운 함수들
getAccumulatedReports, // 누적 현황 조회
getContributorsSummary, // 기여자별 요약
getMyAccumulatedData, // 개인 누적 현황
removeMyEntry, // 개별 항목 삭제 (본인 것만)
// 📊 요약 및 통계 함수들
getDailySummary, // 일일 요약
getMonthlySummary, // 월간 요약
getWorkReportStats, // 통계
// 📋 마스터 데이터 함수들
getWorkTypes, // 작업 유형 목록
getWorkStatusTypes, // 업무 상태 유형 목록
getErrorTypes // 에러 유형 목록
};