feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등

- 순찰/점검 기능 개선 (zone-detail 페이지 추가)
- 출근/근태 시스템 개선 (연차 조회, 근무현황)
- 작업분석 대분류 그룹화 및 마이그레이션 스크립트
- 모바일 네비게이션 UI 추가
- NAS 배포 도구 및 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-09 14:41:01 +09:00
parent 1548253f56
commit 2b1c7bfb88
633 changed files with 361224 additions and 1090 deletions

View File

@@ -0,0 +1,934 @@
/**
* 일일 작업 보고서 컨트롤러
*
* 작업 보고서 API 엔드포인트 핸들러
*
* @author TK-FB-Project
* @since 2025-12-11
*/
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
const dailyWorkReportService = require('../services/dailyWorkReportService');
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
const { asyncHandler } = require('../middlewares/errorHandler');
const logger = require('../utils/logger');
/**
* 📝 작업보고서 생성 (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 || '알 수 없는 사용자'
};
const result = await dailyWorkReportService.createDailyWorkReportService(reportData);
res.status(201).json({
success: true,
data: result,
message: '작업보고서가 성공적으로 생성되었습니다'
});
});
/**
* 📊 기여자별 요약 조회 (새로운 기능)
*/
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;
const user_job_type = req.user?.job_type;
if (!current_user_id) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
const isAdmin = user_access_level === 'system' || user_access_level === 'admin' || user_access_level === 'leader' || user_job_type === 'leader';
console.log(`📊 날짜별 조회 (경로): date=${date}, user=${current_user_id}, 권한=${user_access_level}, 직책=${user_job_type}, 관리자=${isAdmin}`);
console.log(`🔍 사용자 정보 상세:`, req.user);
dailyWorkReportModel.getByDate(date, (err, data) => {
if (err) {
console.error('날짜별 작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
// 🎯 권한별 필터링 (임시로 비활성화)
let finalData = data;
console.log(`📊 임시로 모든 사용자에게 전체 조회 허용: ${data.length}`);
console.log(`📊 권한 정보: access_level=${user_access_level}, job_type=${user_job_type}, isAdmin=${isAdmin}`);
// 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 사용)
* 권한: 그룹장(group_leader), 시스템(system), 관리자(admin)만 가능
*/
const removeDailyWorkReport = async (req, res) => {
try {
const { id: reportId } = req.params;
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
access_level: req.user?.access_level || req.user?.role,
};
if (!userInfo.user_id) {
return res.status(401).json({ error: '사용자 인증 정보가 없습니다.' });
}
// 권한 체크: 그룹장, 시스템, 관리자만 삭제 가능
const allowedRoles = ['admin', 'system', 'group_leader'];
if (!allowedRoles.includes(userInfo.access_level)) {
return res.status(403).json({
error: '작업보고서 삭제 권한이 없습니다.',
details: '그룹장 이상의 권한이 필요합니다.'
});
}
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;
const access_level = req.user?.access_level || req.user?.role;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
// 권한 체크: 그룹장, 시스템, 관리자만 삭제 가능
const allowedRoles = ['admin', 'system', 'group_leader'];
if (!allowedRoles.includes(access_level)) {
return res.status(403).json({
error: '작업보고서 삭제 권한이 없습니다.',
details: '그룹장 이상의 권한이 필요합니다.'
});
}
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({
success: false,
error: {
message: '작업 유형 조회 중 오류가 발생했습니다.',
code: 'DATABASE_ERROR'
}
});
}
console.log(`📋 작업 유형 조회 결과: ${data.length}`);
res.json({
success: true,
data: data,
message: '작업 유형 조회 성공'
});
});
};
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);
});
};
// ========== 작업 유형 CRUD ==========
/**
* 📝 작업 유형 생성
*/
const createWorkType = asyncHandler(async (req, res) => {
const { name, description, category } = req.body;
if (!name) {
throw new ApiError('작업 유형 이름이 필요합니다.', 400);
}
console.log('📝 작업 유형 생성:', { name, description, category });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.createWorkType({ name, description, category }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
res.created(result, '작업 유형이 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 생성');
}
});
/**
* ✏️ 작업 유형 수정
*/
const updateWorkType = asyncHandler(async (req, res) => {
const { id } = req.params;
const { name, description, category } = req.body;
if (!id) {
throw new ApiError('작업 유형 ID가 필요합니다.', 400);
}
console.log('✏️ 작업 유형 수정:', { id, name, description, category });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.updateWorkType(id, { name, description, category }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('수정할 작업 유형을 찾을 수 없습니다.', 404);
}
res.success(result, '작업 유형이 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 수정');
}
});
/**
* 🗑️ 작업 유형 삭제
*/
const deleteWorkType = asyncHandler(async (req, res) => {
const { id } = req.params;
if (!id) {
throw new ApiError('작업 유형 ID가 필요합니다.', 400);
}
console.log('🗑️ 작업 유형 삭제:', id);
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.deleteWorkType(id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('삭제할 작업 유형을 찾을 수 없습니다.', 404);
}
res.success(result, '작업 유형이 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 삭제');
}
});
// ========== 작업 상태 CRUD ==========
/**
* 📝 작업 상태 생성
*/
const createWorkStatus = asyncHandler(async (req, res) => {
const { name, description, is_error } = req.body;
if (!name) {
throw new ApiError('작업 상태 이름이 필요합니다.', 400);
}
console.log('📝 작업 상태 생성:', { name, description, is_error });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.createWorkStatus({ name, description, is_error }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
res.created(result, '작업 상태가 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 생성');
}
});
/**
* ✏️ 작업 상태 수정
*/
const updateWorkStatus = asyncHandler(async (req, res) => {
const { id } = req.params;
const { name, description, is_error } = req.body;
if (!id) {
throw new ApiError('작업 상태 ID가 필요합니다.', 400);
}
console.log('✏️ 작업 상태 수정:', { id, name, description, is_error });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.updateWorkStatus(id, { name, description, is_error }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('수정할 작업 상태를 찾을 수 없습니다.', 404);
}
res.success(result, '작업 상태가 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 수정');
}
});
/**
* 🗑️ 작업 상태 삭제
*/
const deleteWorkStatus = asyncHandler(async (req, res) => {
const { id } = req.params;
if (!id) {
throw new ApiError('작업 상태 ID가 필요합니다.', 400);
}
console.log('🗑️ 작업 상태 삭제:', id);
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.deleteWorkStatus(id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('삭제할 작업 상태를 찾을 수 없습니다.', 404);
}
res.success(result, '작업 상태가 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 삭제');
}
});
// ========== 오류 유형 CRUD ==========
/**
* 📝 오류 유형 생성
*/
const createErrorType = asyncHandler(async (req, res) => {
const { name, description, severity } = req.body;
if (!name) {
throw new ApiError('오류 유형 이름이 필요합니다.', 400);
}
console.log('📝 오류 유형 생성:', { name, description, severity });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.createErrorType({ name, description, severity }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
res.created(result, '오류 유형이 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 생성');
}
});
/**
* ✏️ 오류 유형 수정
*/
const updateErrorType = asyncHandler(async (req, res) => {
const { id } = req.params;
const { name, description, severity } = req.body;
if (!id) {
throw new ApiError('오류 유형 ID가 필요합니다.', 400);
}
console.log('✏️ 오류 유형 수정:', { id, name, description, severity });
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.updateErrorType(id, { name, description, severity }, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('수정할 오류 유형을 찾을 수 없습니다.', 404);
}
res.success(result, '오류 유형이 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 수정');
}
});
/**
* 🗑️ 오류 유형 삭제
*/
const deleteErrorType = asyncHandler(async (req, res) => {
const { id } = req.params;
if (!id) {
throw new ApiError('오류 유형 ID가 필요합니다.', 400);
}
console.log('🗑️ 오류 유형 삭제:', id);
try {
const result = await new Promise((resolve, reject) => {
dailyWorkReportModel.deleteErrorType(id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (result.affectedRows === 0) {
throw new ApiError('삭제할 오류 유형을 찾을 수 없습니다.', 404);
}
res.success(result, '오류 유형이 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 삭제');
}
});
/**
* 📊 누적 현황 조회
*/
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()
});
});
};
/**
* TBM 배정 기반 작업보고서 생성
*/
const createFromTbm = async (req, res) => {
try {
const {
tbm_assignment_id,
tbm_session_id,
worker_id,
project_id,
work_type_id,
report_date,
start_time,
end_time,
total_hours,
error_hours,
error_type_id,
work_status_id
} = req.body;
// 필수 필드 검증
if (!tbm_assignment_id || !tbm_session_id || !worker_id || !report_date || !total_hours) {
return res.status(400).json({
success: false,
message: '필수 필드가 누락되었습니다. (assignment_id, session_id, worker_id, report_date, total_hours)'
});
}
// regular_hours 계산
const regular_hours = total_hours - (error_hours || 0);
const reportData = {
tbm_assignment_id,
tbm_session_id,
worker_id,
project_id,
work_type_id,
report_date,
start_time,
end_time,
total_hours,
error_hours: error_hours || 0,
regular_hours,
work_status_id: work_status_id || (error_hours > 0 ? 2 : 1), // error_hours가 있으면 상태 2 (부적합)
error_type_id,
created_by: req.user.user_id
};
const result = await dailyWorkReportModel.createFromTbmAssignment(reportData);
res.status(201).json({
success: true,
message: '작업보고서가 생성되었습니다.',
data: result
});
} catch (err) {
console.error('TBM 작업보고서 생성 오류:', err);
console.error('Error stack:', err.stack);
res.status(500).json({
success: false,
message: 'TBM 작업보고서 생성 중 오류가 발생했습니다.',
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
}
};
// 모든 컨트롤러 함수 내보내기 (리팩토링된 함수 위주로 재구성)
module.exports = {
// 📝 V2 핵심 CRUD 함수
createDailyWorkReport,
getDailyWorkReports,
updateWorkReport,
removeDailyWorkReport,
createFromTbm,
// 📊 V2 통계 및 요약 함수
getWorkReportStats,
getDailySummary,
// 🔽 아직 리팩토링되지 않은 레거시 함수들
getAccumulatedReports,
getContributorsSummary,
getMyAccumulatedData,
removeMyEntry,
getDailyWorkReportsByDate,
searchWorkReports,
getMonthlySummary,
removeDailyWorkReportByDateAndWorker,
getWorkTypes,
getWorkStatusTypes,
getErrorTypes,
// 🔽 마스터 데이터 CRUD
createWorkType,
updateWorkType,
deleteWorkType,
createWorkStatus,
updateWorkStatus,
deleteWorkStatus,
createErrorType,
updateErrorType,
deleteErrorType
};