sso_users.user_id를 단일 식별자로 통합. JWT에서 worker_id 제거, department_id/is_production 추가. 백엔드 15개 모델, 11개 컨트롤러, 4개 서비스, 7개 라우트, 프론트엔드 32+ JS/11+ HTML 변환. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
776 lines
21 KiB
JavaScript
776 lines
21 KiB
JavaScript
/**
|
|
* 일일 작업 보고서 컨트롤러
|
|
*
|
|
* 작업 보고서 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, user_id } = req.query;
|
|
|
|
if (!date || !user_id) {
|
|
return res.status(400).json({ error: 'date와 user_id가 필요합니다.' });
|
|
}
|
|
|
|
const data = await dailyWorkReportModel.getContributorsByDate(date, user_id);
|
|
|
|
const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
|
|
|
|
const result = {
|
|
date,
|
|
user_id,
|
|
contributors: data,
|
|
total_contributors: data.length,
|
|
grand_total_hours: totalHours
|
|
};
|
|
|
|
res.success(result, '기여자별 요약 조회 성공');
|
|
});
|
|
|
|
/**
|
|
* 개인 누적 현황 조회
|
|
*/
|
|
const getMyAccumulatedData = async (req, res) => {
|
|
const { date, user_id } = req.query;
|
|
const created_by = req.user?.user_id || req.user?.id;
|
|
|
|
if (!date || !user_id) {
|
|
return res.status(400).json({
|
|
error: 'date와 user_id가 필요합니다.',
|
|
example: 'date=2024-06-16&user_id=1'
|
|
});
|
|
}
|
|
|
|
if (!created_by) {
|
|
return res.status(401).json({
|
|
error: '사용자 인증 정보가 없습니다.'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const data = await dailyWorkReportModel.getMyAccumulatedHours(date, user_id, created_by);
|
|
|
|
res.json({
|
|
date,
|
|
user_id,
|
|
created_by,
|
|
my_data: data,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (err) {
|
|
logger.error('개인 누적 현황 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '개인 누적 현황 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 개별 항목 삭제 (본인 작성분만)
|
|
*/
|
|
const removeMyEntry = async (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: '사용자 인증 정보가 없습니다.'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const result = await dailyWorkReportModel.removeSpecificEntry(id, deleted_by);
|
|
|
|
res.json({
|
|
message: '항목이 성공적으로 삭제되었습니다.',
|
|
id: id,
|
|
deleted_by,
|
|
timestamp: new Date().toISOString(),
|
|
...result
|
|
});
|
|
} catch (err) {
|
|
logger.error('개별 항목 삭제 오류:', err);
|
|
res.status(500).json({
|
|
error: '항목 삭제 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 작업보고서 조회 (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'
|
|
};
|
|
|
|
if (!userInfo.user_id) {
|
|
return res.status(401).json({ error: '사용자 인증 정보가 없습니다.' });
|
|
}
|
|
|
|
const reports = await dailyWorkReportService.getDailyWorkReportsService(req.query, userInfo);
|
|
|
|
res.json(reports);
|
|
|
|
} catch (error) {
|
|
logger.error('작업보고서 조회 컨트롤러 오류:', error.message);
|
|
res.status(400).json({
|
|
success: false,
|
|
error: '작업보고서 조회에 실패했습니다.',
|
|
details: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
|
|
*/
|
|
const getDailyWorkReportsByDate = async (req, res) => {
|
|
const { date } = req.params;
|
|
const current_user_id = req.user?.user_id || req.user?.id;
|
|
|
|
if (!current_user_id) {
|
|
return res.status(401).json({
|
|
error: '사용자 인증 정보가 없습니다.'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const data = await dailyWorkReportModel.getByDate(date);
|
|
|
|
// 임시로 모든 사용자에게 전체 조회 허용
|
|
res.json(data);
|
|
} catch (err) {
|
|
logger.error('날짜별 작업보고서 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '작업보고서 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 작업보고서 검색 (페이지네이션 포함)
|
|
*/
|
|
const searchWorkReports = async (req, res) => {
|
|
const { start_date, end_date, user_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: ['user_id', 'project_id', 'work_status_id', 'page', 'limit']
|
|
});
|
|
}
|
|
|
|
if (!created_by) {
|
|
return res.status(401).json({
|
|
error: '사용자 인증 정보가 없습니다.'
|
|
});
|
|
}
|
|
|
|
const searchParams = {
|
|
start_date,
|
|
end_date,
|
|
user_id: user_id ? parseInt(user_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)
|
|
};
|
|
|
|
try {
|
|
const data = await dailyWorkReportModel.searchWithDetails(searchParams);
|
|
res.json(data);
|
|
} catch (err) {
|
|
logger.error('작업보고서 검색 오류:', err);
|
|
res.status(500).json({
|
|
error: '작업보고서 검색 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 통계 조회 (V2 - Service Layer 사용)
|
|
*/
|
|
const getWorkReportStats = async (req, res) => {
|
|
try {
|
|
const statsData = await dailyWorkReportService.getStatisticsService(req.query);
|
|
res.json(statsData);
|
|
} catch (error) {
|
|
logger.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) {
|
|
logger.error('일일 요약 조회 컨트롤러 오류:', error.message);
|
|
res.status(400).json({
|
|
success: false,
|
|
error: '일일 요약 조회에 실패했습니다.',
|
|
details: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 월간 요약 조회
|
|
*/
|
|
const getMonthlySummary = async (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 형식으로 입력하세요.'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const data = await dailyWorkReportModel.getMonthlySummary(year, month);
|
|
|
|
res.json({
|
|
year: parseInt(year),
|
|
month: parseInt(month),
|
|
summary: data,
|
|
total_entries: data.length,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (err) {
|
|
logger.error('월간 요약 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '월간 요약 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 작업보고서 수정 (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) {
|
|
logger.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) {
|
|
logger.error(`작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
|
|
const statusCode = error.statusCode || 400;
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
error: '작업보고서 삭제에 실패했습니다.',
|
|
details: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 작업자의 특정 날짜 전체 삭제
|
|
*/
|
|
const removeDailyWorkReportByDateAndWorker = async (req, res) => {
|
|
const { date, user_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: '그룹장 이상의 권한이 필요합니다.'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, user_id, deleted_by);
|
|
|
|
if (affectedRows === 0) {
|
|
return res.status(404).json({
|
|
error: '삭제할 작업보고서를 찾을 수 없습니다.',
|
|
date: date,
|
|
user_id: user_id
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
message: `${date} 날짜의 작업자 ${user_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`,
|
|
date,
|
|
user_id,
|
|
affected_rows: affectedRows,
|
|
deleted_by,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (err) {
|
|
logger.error('작업보고서 전체 삭제 오류:', err);
|
|
res.status(500).json({
|
|
error: '작업보고서 삭제 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 마스터 데이터 조회 함수들
|
|
*/
|
|
const getWorkTypes = async (req, res) => {
|
|
try {
|
|
const data = await dailyWorkReportModel.getAllWorkTypes();
|
|
res.json({
|
|
success: true,
|
|
data: data,
|
|
message: '작업 유형 조회 성공'
|
|
});
|
|
} catch (err) {
|
|
logger.error('작업 유형 조회 오류:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: {
|
|
message: '작업 유형 조회 중 오류가 발생했습니다.',
|
|
code: 'DATABASE_ERROR'
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const getWorkStatusTypes = async (req, res) => {
|
|
try {
|
|
const data = await dailyWorkReportModel.getAllWorkStatusTypes();
|
|
res.json(data);
|
|
} catch (err) {
|
|
logger.error('업무 상태 유형 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '업무 상태 유형 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
const getErrorTypes = async (req, res) => {
|
|
try {
|
|
const data = await dailyWorkReportModel.getAllErrorTypes();
|
|
res.json(data);
|
|
} catch (err) {
|
|
logger.error('에러 유형 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '에러 유형 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
// ========== 작업 유형 CRUD ==========
|
|
|
|
/**
|
|
* 작업 유형 생성
|
|
*/
|
|
const createWorkType = asyncHandler(async (req, res) => {
|
|
const { name, description, category } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: '작업 유형 이름이 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.createWorkType({ name, description, category });
|
|
res.created(result, '작업 유형이 성공적으로 생성되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 작업 유형 수정
|
|
*/
|
|
const updateWorkType = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, description, category } = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.updateWorkType(id, { name, description, category });
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '수정할 작업 유형을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '작업 유형이 성공적으로 수정되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 작업 유형 삭제
|
|
*/
|
|
const deleteWorkType = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.deleteWorkType(id);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '삭제할 작업 유형을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '작업 유형이 성공적으로 삭제되었습니다.');
|
|
});
|
|
|
|
// ========== 작업 상태 CRUD ==========
|
|
|
|
/**
|
|
* 작업 상태 생성
|
|
*/
|
|
const createWorkStatus = asyncHandler(async (req, res) => {
|
|
const { name, description, is_error } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: '작업 상태 이름이 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.createWorkStatus({ name, description, is_error });
|
|
res.created(result, '작업 상태가 성공적으로 생성되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 작업 상태 수정
|
|
*/
|
|
const updateWorkStatus = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, description, is_error } = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.updateWorkStatus(id, { name, description, is_error });
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '수정할 작업 상태를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '작업 상태가 성공적으로 수정되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 작업 상태 삭제
|
|
*/
|
|
const deleteWorkStatus = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.deleteWorkStatus(id);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '삭제할 작업 상태를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '작업 상태가 성공적으로 삭제되었습니다.');
|
|
});
|
|
|
|
// ========== 오류 유형 CRUD ==========
|
|
|
|
/**
|
|
* 오류 유형 생성
|
|
*/
|
|
const createErrorType = asyncHandler(async (req, res) => {
|
|
const { name, description, severity } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: '오류 유형 이름이 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.createErrorType({ name, description, severity });
|
|
res.created(result, '오류 유형이 성공적으로 생성되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 오류 유형 수정
|
|
*/
|
|
const updateErrorType = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, description, severity } = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.updateErrorType(id, { name, description, severity });
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '수정할 오류 유형을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '오류 유형이 성공적으로 수정되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 오류 유형 삭제
|
|
*/
|
|
const deleteErrorType = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
|
|
}
|
|
|
|
const result = await dailyWorkReportModel.deleteErrorType(id);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: '삭제할 오류 유형을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.success(result, '오류 유형이 성공적으로 삭제되었습니다.');
|
|
});
|
|
|
|
/**
|
|
* 누적 현황 조회
|
|
*/
|
|
const getAccumulatedReports = async (req, res) => {
|
|
const { date, user_id } = req.query;
|
|
|
|
if (!date || !user_id) {
|
|
return res.status(400).json({
|
|
error: 'date와 user_id가 필요합니다.',
|
|
example: 'date=2024-06-16&user_id=1'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const data = await dailyWorkReportModel.getAccumulatedReportsByDate(date, user_id);
|
|
|
|
res.json({
|
|
date,
|
|
user_id,
|
|
total_entries: data.length,
|
|
accumulated_data: data,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (err) {
|
|
logger.error('누적 현황 조회 오류:', err);
|
|
res.status(500).json({
|
|
error: '누적 현황 조회 중 오류가 발생했습니다.',
|
|
details: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* TBM 배정 기반 작업보고서 생성
|
|
*/
|
|
const createFromTbm = async (req, res) => {
|
|
try {
|
|
const {
|
|
tbm_assignment_id,
|
|
tbm_session_id,
|
|
user_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 || !user_id || !report_date || !total_hours) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '필수 필드가 누락되었습니다. (assignment_id, session_id, user_id, report_date, total_hours)'
|
|
});
|
|
}
|
|
|
|
// regular_hours 계산
|
|
const regular_hours = total_hours - (error_hours || 0);
|
|
|
|
const reportData = {
|
|
tbm_assignment_id,
|
|
tbm_session_id,
|
|
user_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_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) {
|
|
logger.error('TBM 작업보고서 생성 오류:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'TBM 작업보고서 생성 중 오류가 발생했습니다.',
|
|
error: err.message
|
|
});
|
|
}
|
|
};
|
|
|
|
// 모든 컨트롤러 함수 내보내기
|
|
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
|
|
};
|