Files
TK-FB-Project/deploy/tkfb-package/api.hyungi.net/controllers/dailyWorkReportController.js
Hyungi Ahn 2b1c7bfb88 feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가)
- 출근/근태 시스템 개선 (연차 조회, 근무현황)
- 작업분석 대분류 그룹화 및 마이그레이션 스크립트
- 모바일 네비게이션 UI 추가
- NAS 배포 도구 및 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:41:01 +09:00

934 lines
27 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.
/**
* 일일 작업 보고서 컨트롤러
*
* 작업 보고서 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
};