refactor: System 1 모델/컨트롤러 콜백→async/await 전환

11개 모델 파일의 171개 콜백 메서드를 직접 return/throw 패턴으로 변환.
8개 컨트롤러에서 new Promise 래퍼와 중첩 콜백 제거, console.error→logger.error 교체.
미사용 pageAccessModel.js 삭제. 전체 -3,600줄 감소.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-25 09:40:33 +09:00
parent c29a1506bb
commit 5183e9ff85
22 changed files with 4791 additions and 8406 deletions

View File

@@ -14,7 +14,7 @@ const { asyncHandler } = require('../middlewares/errorHandler');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
/** /**
* 📝 작업보고서 생성 (V2 - Service Layer 사용) * 작업보고서 생성 (V2 - Service Layer 사용)
*/ */
const createDailyWorkReport = asyncHandler(async (req, res) => { const createDailyWorkReport = asyncHandler(async (req, res) => {
const reportData = { const reportData = {
@@ -33,29 +33,19 @@ const createDailyWorkReport = asyncHandler(async (req, res) => {
}); });
/** /**
* 📊 기여자별 요약 조회 (새로운 기능) * 기여자별 요약 조회
*/ */
const getContributorsSummary = asyncHandler(async (req, res) => { const getContributorsSummary = asyncHandler(async (req, res) => {
const { date, worker_id } = req.query; const { date, worker_id } = req.query;
if (!date || !worker_id) { if (!date || !worker_id) {
throw new ApiError('date와 worker_id가 필요합니다.', 400); return res.status(400).json({ error: 'date와 worker_id가 필요합니다.' });
} }
console.log(`📊 기여자별 요약 조회: date=${date}, worker_id=${worker_id}`); const data = await dailyWorkReportModel.getContributorsByDate(date, 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); const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`);
const result = { const result = {
date, date,
worker_id, worker_id,
@@ -65,15 +55,12 @@ const getContributorsSummary = asyncHandler(async (req, res) => {
}; };
res.success(result, '기여자별 요약 조회 성공'); res.success(result, '기여자별 요약 조회 성공');
} catch (err) {
handleDatabaseError(err, '기여자별 요약 조회');
}
}); });
/** /**
* 📊 개인 누적 현황 조회 (새로운 기능) * 개인 누적 현황 조회
*/ */
const getMyAccumulatedData = (req, res) => { const getMyAccumulatedData = async (req, res) => {
const { date, worker_id } = req.query; const { date, worker_id } = req.query;
const created_by = req.user?.user_id || req.user?.id; const created_by = req.user?.user_id || req.user?.id;
@@ -90,18 +77,9 @@ const getMyAccumulatedData = (req, res) => {
}); });
} }
console.log(`📊 개인 누적 현황 조회: date=${date}, worker_id=${worker_id}, created_by=${created_by}`); try {
const data = await dailyWorkReportModel.getMyAccumulatedHours(date, worker_id, 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({ res.json({
date, date,
worker_id, worker_id,
@@ -109,13 +87,19 @@ const getMyAccumulatedData = (req, res) => {
my_data: data, my_data: data,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (err) {
logger.error('개인 누적 현황 조회 오류:', err);
res.status(500).json({
error: '개인 누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
}); });
}
}; };
/** /**
* 🗑️ 개별 항목 삭제 (본인 작성분만 - 새로운 기능) * 개별 항목 삭제 (본인 작성분만)
*/ */
const removeMyEntry = (req, res) => { const removeMyEntry = async (req, res) => {
const { id } = req.params; const { id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id; const deleted_by = req.user?.user_id || req.user?.id;
@@ -125,18 +109,9 @@ const removeMyEntry = (req, res) => {
}); });
} }
console.log(`🗑️ 개별 항목 삭제 요청: id=${id}, 삭제자=${deleted_by}`); try {
const result = await dailyWorkReportModel.removeSpecificEntry(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({ res.json({
message: '항목이 성공적으로 삭제되었습니다.', message: '항목이 성공적으로 삭제되었습니다.',
id: id, id: id,
@@ -144,17 +119,23 @@ const removeMyEntry = (req, res) => {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
...result ...result
}); });
} catch (err) {
logger.error('개별 항목 삭제 오류:', err);
res.status(500).json({
error: '항목 삭제 중 오류가 발생했습니다.',
details: err.message
}); });
}
}; };
/** /**
* 📊 작업보고서 조회 (V2 - Service Layer 사용) * 작업보고서 조회 (V2 - Service Layer 사용)
*/ */
const getDailyWorkReports = async (req, res) => { const getDailyWorkReports = async (req, res) => {
try { try {
const userInfo = { const userInfo = {
user_id: req.user?.user_id || req.user?.id, user_id: req.user?.user_id || req.user?.id,
role: req.user?.role || 'user' // 기본값을 'user'로 설정하여 안전하게 처리 role: req.user?.role || 'user'
}; };
if (!userInfo.user_id) { if (!userInfo.user_id) {
@@ -166,7 +147,7 @@ const getDailyWorkReports = async (req, res) => {
res.json(reports); res.json(reports);
} catch (error) { } catch (error) {
console.error('💥 작업보고서 조회 컨트롤러 오류:', error.message); logger.error('작업보고서 조회 컨트롤러 오류:', error.message);
res.status(400).json({ res.status(400).json({
success: false, success: false,
error: '작업보고서 조회에 실패했습니다.', error: '작업보고서 조회에 실패했습니다.',
@@ -176,13 +157,11 @@ const getDailyWorkReports = async (req, res) => {
}; };
/** /**
* 📊 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원) * 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
*/ */
const getDailyWorkReportsByDate = (req, res) => { const getDailyWorkReportsByDate = async (req, res) => {
const { date } = req.params; const { date } = req.params;
const current_user_id = req.user?.user_id || req.user?.id; 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) { if (!current_user_id) {
return res.status(401).json({ return res.status(401).json({
@@ -190,40 +169,24 @@ const getDailyWorkReportsByDate = (req, res) => {
}); });
} }
const isAdmin = user_access_level === 'system' || user_access_level === 'admin' || user_access_level === 'leader' || user_job_type === 'leader'; try {
const data = await dailyWorkReportModel.getByDate(date);
console.log(`📊 날짜별 조회 (경로): date=${date}, user=${current_user_id}, 권한=${user_access_level}, 직책=${user_job_type}, 관리자=${isAdmin}`); // 임시로 모든 사용자에게 전체 조회 허용
console.log(`🔍 사용자 정보 상세:`, req.user); res.json(data);
} catch (err) {
dailyWorkReportModel.getByDate(date, (err, data) => { logger.error('날짜별 작업보고서 조회 오류:', err);
if (err) { res.status(500).json({
console.error('날짜별 작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.', error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message 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 searchWorkReports = async (req, res) => {
const { start_date, end_date, worker_id, project_id, work_status_id, page = 1, limit = 20 } = req.query; 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; const created_by = req.user?.user_id || req.user?.id;
@@ -247,36 +210,32 @@ const searchWorkReports = (req, res) => {
worker_id: worker_id ? parseInt(worker_id) : null, worker_id: worker_id ? parseInt(worker_id) : null,
project_id: project_id ? parseInt(project_id) : null, project_id: project_id ? parseInt(project_id) : null,
work_status_id: work_status_id ? parseInt(work_status_id) : null, work_status_id: work_status_id ? parseInt(work_status_id) : null,
created_by, // 작성자 필터링 추가 created_by,
page: parseInt(page), page: parseInt(page),
limit: parseInt(limit) limit: parseInt(limit)
}; };
console.log('🔍 작업보고서 검색 요청:', searchParams); try {
const data = await dailyWorkReportModel.searchWithDetails(searchParams);
dailyWorkReportModel.searchWithDetails(searchParams, (err, data) => { res.json(data);
if (err) { } catch (err) {
console.error('작업보고서 검색 오류:', err); logger.error('작업보고서 검색 오류:', err);
return res.status(500).json({ res.status(500).json({
error: '작업보고서 검색 중 오류가 발생했습니다.', error: '작업보고서 검색 중 오류가 발생했습니다.',
details: err.message details: err.message
}); });
} }
console.log(`🔍 검색 결과: ${data.reports?.length || 0}개 (전체: ${data.total || 0}개)`);
res.json(data);
});
}; };
/** /**
* 📈 통계 조회 (V2 - Service Layer 사용) * 통계 조회 (V2 - Service Layer 사용)
*/ */
const getWorkReportStats = async (req, res) => { const getWorkReportStats = async (req, res) => {
try { try {
const statsData = await dailyWorkReportService.getStatisticsService(req.query); const statsData = await dailyWorkReportService.getStatisticsService(req.query);
res.json(statsData); res.json(statsData);
} catch (error) { } catch (error) {
console.error('💥 통계 조회 컨트롤러 오류:', error.message); logger.error('통계 조회 컨트롤러 오류:', error.message);
res.status(400).json({ res.status(400).json({
success: false, success: false,
error: '통계 조회에 실패했습니다.', error: '통계 조회에 실패했습니다.',
@@ -286,14 +245,14 @@ const getWorkReportStats = async (req, res) => {
}; };
/** /**
* 📊 일일 근무 요약 조회 (V2 - Service Layer 사용) * 일일 근무 요약 조회 (V2 - Service Layer 사용)
*/ */
const getDailySummary = async (req, res) => { const getDailySummary = async (req, res) => {
try { try {
const summaryData = await dailyWorkReportService.getSummaryService(req.query); const summaryData = await dailyWorkReportService.getSummaryService(req.query);
res.json(summaryData); res.json(summaryData);
} catch (error) { } catch (error) {
console.error('💥 일일 요약 조회 컨트롤러 오류:', error.message); logger.error('일일 요약 조회 컨트롤러 오류:', error.message);
res.status(400).json({ res.status(400).json({
success: false, success: false,
error: '일일 요약 조회에 실패했습니다.', error: '일일 요약 조회에 실패했습니다.',
@@ -303,9 +262,9 @@ const getDailySummary = async (req, res) => {
}; };
/** /**
* 📅 월간 요약 조회 * 월간 요약 조회
*/ */
const getMonthlySummary = (req, res) => { const getMonthlySummary = async (req, res) => {
const { year, month } = req.query; const { year, month } = req.query;
if (!year || !month) { if (!year || !month) {
@@ -316,16 +275,8 @@ const getMonthlySummary = (req, res) => {
}); });
} }
console.log(`📅 월간 요약 조회: ${year}-${month}`); try {
const data = await dailyWorkReportModel.getMonthlySummary(year, month);
dailyWorkReportModel.getMonthlySummary(year, month, (err, data) => {
if (err) {
console.error('월간 요약 조회 오류:', err);
return res.status(500).json({
error: '월간 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json({ res.json({
year: parseInt(year), year: parseInt(year),
@@ -334,11 +285,17 @@ const getMonthlySummary = (req, res) => {
total_entries: data.length, total_entries: data.length,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (err) {
logger.error('월간 요약 조회 오류:', err);
res.status(500).json({
error: '월간 요약 조회 중 오류가 발생했습니다.',
details: err.message
}); });
}
}; };
/** /**
* ✏️ 작업보고서 수정 (V2 - Service Layer 사용) * 작업보고서 수정 (V2 - Service Layer 사용)
*/ */
const updateWorkReport = async (req, res) => { const updateWorkReport = async (req, res) => {
try { try {
@@ -362,7 +319,7 @@ const updateWorkReport = async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error(`💥 작업보고서 수정 컨트롤러 오류 (id: ${req.params.id}):`, error.message); logger.error(`작업보고서 수정 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
const statusCode = error.statusCode || 400; const statusCode = error.statusCode || 400;
res.status(statusCode).json({ res.status(statusCode).json({
success: false, success: false,
@@ -373,7 +330,7 @@ const updateWorkReport = async (req, res) => {
}; };
/** /**
* 🗑️ 특정 작업보고서 삭제 (V2 - Service Layer 사용) * 특정 작업보고서 삭제 (V2 - Service Layer 사용)
* 권한: 그룹장(group_leader), 시스템(system), 관리자(admin)만 가능 * 권한: 그룹장(group_leader), 시스템(system), 관리자(admin)만 가능
*/ */
const removeDailyWorkReport = async (req, res) => { const removeDailyWorkReport = async (req, res) => {
@@ -406,7 +363,7 @@ const removeDailyWorkReport = async (req, res) => {
}); });
} catch (error) { } catch (error) {
console.error(`💥 작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message); logger.error(`작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
const statusCode = error.statusCode || 400; const statusCode = error.statusCode || 400;
res.status(statusCode).json({ res.status(statusCode).json({
success: false, success: false,
@@ -417,9 +374,9 @@ const removeDailyWorkReport = async (req, res) => {
}; };
/** /**
* <20><> 작업자의 특정 날짜 전체 삭제 * 작업자의 특정 날짜 전체 삭제
*/ */
const removeDailyWorkReportByDateAndWorker = (req, res) => { const removeDailyWorkReportByDateAndWorker = async (req, res) => {
const { date, worker_id } = req.params; const { date, worker_id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id; const deleted_by = req.user?.user_id || req.user?.id;
const access_level = req.user?.access_level || req.user?.role; const access_level = req.user?.access_level || req.user?.role;
@@ -439,16 +396,8 @@ const removeDailyWorkReportByDateAndWorker = (req, res) => {
}); });
} }
console.log(`🗑️ 날짜+작업자별 전체 삭제 요청: date=${date}, worker_id=${worker_id}, 삭제자=${deleted_by}`); try {
const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, 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) { if (affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({
@@ -458,7 +407,6 @@ const removeDailyWorkReportByDateAndWorker = (req, res) => {
}); });
} }
console.log(`✅ 날짜+작업자별 전체 삭제 완료: ${affectedRows}`);
res.json({ res.json({
message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`, message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`,
date, date,
@@ -467,18 +415,29 @@ const removeDailyWorkReportByDateAndWorker = (req, res) => {
deleted_by, deleted_by,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (err) {
logger.error('작업보고서 전체 삭제 오류:', err);
res.status(500).json({
error: '작업보고서 삭제 중 오류가 발생했습니다.',
details: err.message
}); });
}
}; };
/** /**
* 📋 마스터 데이터 조회 함수들 * 마스터 데이터 조회 함수들
*/ */
const getWorkTypes = (req, res) => { const getWorkTypes = async (req, res) => {
console.log('📋 작업 유형 조회 요청'); try {
dailyWorkReportModel.getAllWorkTypes((err, data) => { const data = await dailyWorkReportModel.getAllWorkTypes();
if (err) { res.json({
console.error('작업 유형 조회 오류:', err); success: true,
return res.status(500).json({ data: data,
message: '작업 유형 조회 성공'
});
} catch (err) {
logger.error('작업 유형 조회 오류:', err);
res.status(500).json({
success: false, success: false,
error: { error: {
message: '작업 유형 조회 중 오류가 발생했습니다.', message: '작업 유형 조회 중 오류가 발생했습니다.',
@@ -486,316 +445,203 @@ const getWorkTypes = (req, res) => {
} }
}); });
} }
console.log(`📋 작업 유형 조회 결과: ${data.length}`);
res.json({
success: true,
data: data,
message: '작업 유형 조회 성공'
});
});
}; };
const getWorkStatusTypes = (req, res) => { const getWorkStatusTypes = async (req, res) => {
console.log('📋 업무 상태 유형 조회 요청'); try {
dailyWorkReportModel.getAllWorkStatusTypes((err, data) => { const data = await dailyWorkReportModel.getAllWorkStatusTypes();
if (err) { res.json(data);
console.error('업무 상태 유형 조회 오류:', err); } catch (err) {
return res.status(500).json({ logger.error('업무 상태 유형 조회 오류:', err);
res.status(500).json({
error: '업무 상태 유형 조회 중 오류가 발생했습니다.', error: '업무 상태 유형 조회 중 오류가 발생했습니다.',
details: err.message details: err.message
}); });
} }
console.log(`📋 업무 상태 유형 조회 결과: ${data.length}`);
res.json(data);
});
}; };
const getErrorTypes = (req, res) => { const getErrorTypes = async (req, res) => {
console.log('📋 에러 유형 조회 요청'); try {
dailyWorkReportModel.getAllErrorTypes((err, data) => { const data = await dailyWorkReportModel.getAllErrorTypes();
if (err) { res.json(data);
console.error('에러 유형 조회 오류:', err); } catch (err) {
return res.status(500).json({ logger.error('에러 유형 조회 오류:', err);
res.status(500).json({
error: '에러 유형 조회 중 오류가 발생했습니다.', error: '에러 유형 조회 중 오류가 발생했습니다.',
details: err.message details: err.message
}); });
} }
console.log(`📋 에러 유형 조회 결과: ${data.length}`);
res.json(data);
});
}; };
// ========== 작업 유형 CRUD ========== // ========== 작업 유형 CRUD ==========
/** /**
* 📝 작업 유형 생성 * 작업 유형 생성
*/ */
const createWorkType = asyncHandler(async (req, res) => { const createWorkType = asyncHandler(async (req, res) => {
const { name, description, category } = req.body; const { name, description, category } = req.body;
if (!name) { if (!name) {
throw new ApiError('작업 유형 이름이 필요합니다.', 400); return res.status(400).json({ error: '작업 유형 이름이 필요합니다.' });
} }
console.log('📝 작업 유형 생성:', { name, description, category }); const result = await dailyWorkReportModel.createWorkType({ 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, '작업 유형이 성공적으로 생성되었습니다.'); res.created(result, '작업 유형이 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 생성');
}
}); });
/** /**
* ✏️ 작업 유형 수정 * 작업 유형 수정
*/ */
const updateWorkType = asyncHandler(async (req, res) => { const updateWorkType = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
const { name, description, category } = req.body; const { name, description, category } = req.body;
if (!id) { if (!id) {
throw new ApiError('작업 유형 ID가 필요합니다.', 400); return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
} }
console.log('✏️ 작업 유형 수정:', { id, name, description, category }); const result = await dailyWorkReportModel.updateWorkType(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) { if (result.affectedRows === 0) {
throw new ApiError('수정할 작업 유형을 찾을 수 없습니다.', 404); return res.status(404).json({ error: '수정할 작업 유형을 찾을 수 없습니다.' });
} }
res.success(result, '작업 유형이 성공적으로 수정되었습니다.'); res.success(result, '작업 유형이 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 수정');
}
}); });
/** /**
* 🗑️ 작업 유형 삭제 * 작업 유형 삭제
*/ */
const deleteWorkType = asyncHandler(async (req, res) => { const deleteWorkType = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
if (!id) { if (!id) {
throw new ApiError('작업 유형 ID가 필요합니다.', 400); return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
} }
console.log('🗑️ 작업 유형 삭제:', id); const result = await dailyWorkReportModel.deleteWorkType(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) { if (result.affectedRows === 0) {
throw new ApiError('삭제할 작업 유형을 찾을 수 없습니다.', 404); return res.status(404).json({ error: '삭제할 작업 유형을 찾을 수 없습니다.' });
} }
res.success(result, '작업 유형이 성공적으로 삭제되었습니다.'); res.success(result, '작업 유형이 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 유형 삭제');
}
}); });
// ========== 작업 상태 CRUD ========== // ========== 작업 상태 CRUD ==========
/** /**
* 📝 작업 상태 생성 * 작업 상태 생성
*/ */
const createWorkStatus = asyncHandler(async (req, res) => { const createWorkStatus = asyncHandler(async (req, res) => {
const { name, description, is_error } = req.body; const { name, description, is_error } = req.body;
if (!name) { if (!name) {
throw new ApiError('작업 상태 이름이 필요합니다.', 400); return res.status(400).json({ error: '작업 상태 이름이 필요합니다.' });
} }
console.log('📝 작업 상태 생성:', { name, description, is_error }); const result = await dailyWorkReportModel.createWorkStatus({ 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, '작업 상태가 성공적으로 생성되었습니다.'); res.created(result, '작업 상태가 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 생성');
}
}); });
/** /**
* ✏️ 작업 상태 수정 * 작업 상태 수정
*/ */
const updateWorkStatus = asyncHandler(async (req, res) => { const updateWorkStatus = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
const { name, description, is_error } = req.body; const { name, description, is_error } = req.body;
if (!id) { if (!id) {
throw new ApiError('작업 상태 ID가 필요합니다.', 400); return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
} }
console.log('✏️ 작업 상태 수정:', { id, name, description, is_error }); const result = await dailyWorkReportModel.updateWorkStatus(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) { if (result.affectedRows === 0) {
throw new ApiError('수정할 작업 상태를 찾을 수 없습니다.', 404); return res.status(404).json({ error: '수정할 작업 상태를 찾을 수 없습니다.' });
} }
res.success(result, '작업 상태가 성공적으로 수정되었습니다.'); res.success(result, '작업 상태가 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 수정');
}
}); });
/** /**
* 🗑️ 작업 상태 삭제 * 작업 상태 삭제
*/ */
const deleteWorkStatus = asyncHandler(async (req, res) => { const deleteWorkStatus = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
if (!id) { if (!id) {
throw new ApiError('작업 상태 ID가 필요합니다.', 400); return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
} }
console.log('🗑️ 작업 상태 삭제:', id); const result = await dailyWorkReportModel.deleteWorkStatus(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) { if (result.affectedRows === 0) {
throw new ApiError('삭제할 작업 상태를 찾을 수 없습니다.', 404); return res.status(404).json({ error: '삭제할 작업 상태를 찾을 수 없습니다.' });
} }
res.success(result, '작업 상태가 성공적으로 삭제되었습니다.'); res.success(result, '작업 상태가 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '작업 상태 삭제');
}
}); });
// ========== 오류 유형 CRUD ========== // ========== 오류 유형 CRUD ==========
/** /**
* 📝 오류 유형 생성 * 오류 유형 생성
*/ */
const createErrorType = asyncHandler(async (req, res) => { const createErrorType = asyncHandler(async (req, res) => {
const { name, description, severity } = req.body; const { name, description, severity } = req.body;
if (!name) { if (!name) {
throw new ApiError('오류 유형 이름이 필요합니다.', 400); return res.status(400).json({ error: '오류 유형 이름이 필요합니다.' });
} }
console.log('📝 오류 유형 생성:', { name, description, severity }); const result = await dailyWorkReportModel.createErrorType({ 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, '오류 유형이 성공적으로 생성되었습니다.'); res.created(result, '오류 유형이 성공적으로 생성되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 생성');
}
}); });
/** /**
* ✏️ 오류 유형 수정 * 오류 유형 수정
*/ */
const updateErrorType = asyncHandler(async (req, res) => { const updateErrorType = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
const { name, description, severity } = req.body; const { name, description, severity } = req.body;
if (!id) { if (!id) {
throw new ApiError('오류 유형 ID가 필요합니다.', 400); return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
} }
console.log('✏️ 오류 유형 수정:', { id, name, description, severity }); const result = await dailyWorkReportModel.updateErrorType(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) { if (result.affectedRows === 0) {
throw new ApiError('수정할 오류 유형을 찾을 수 없습니다.', 404); return res.status(404).json({ error: '수정할 오류 유형을 찾을 수 없습니다.' });
} }
res.success(result, '오류 유형이 성공적으로 수정되었습니다.'); res.success(result, '오류 유형이 성공적으로 수정되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 수정');
}
}); });
/** /**
* 🗑️ 오류 유형 삭제 * 오류 유형 삭제
*/ */
const deleteErrorType = asyncHandler(async (req, res) => { const deleteErrorType = asyncHandler(async (req, res) => {
const { id } = req.params; const { id } = req.params;
if (!id) { if (!id) {
throw new ApiError('오류 유형 ID가 필요합니다.', 400); return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
} }
console.log('🗑️ 오류 유형 삭제:', id); const result = await dailyWorkReportModel.deleteErrorType(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) { if (result.affectedRows === 0) {
throw new ApiError('삭제할 오류 유형을 찾을 수 없습니다.', 404); return res.status(404).json({ error: '삭제할 오류 유형을 찾을 수 없습니다.' });
} }
res.success(result, '오류 유형이 성공적으로 삭제되었습니다.'); res.success(result, '오류 유형이 성공적으로 삭제되었습니다.');
} catch (err) {
handleDatabaseError(err, '오류 유형 삭제');
}
}); });
/** /**
* 📊 누적 현황 조회 * 누적 현황 조회
*/ */
const getAccumulatedReports = (req, res) => { const getAccumulatedReports = async (req, res) => {
const { date, worker_id } = req.query; const { date, worker_id } = req.query;
if (!date || !worker_id) { if (!date || !worker_id) {
@@ -805,18 +651,9 @@ const getAccumulatedReports = (req, res) => {
}); });
} }
console.log(`📊 누적 현황 조회: date=${date}, worker_id=${worker_id}`); try {
const data = await dailyWorkReportModel.getAccumulatedReportsByDate(date, 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({ res.json({
date, date,
worker_id, worker_id,
@@ -824,7 +661,13 @@ const getAccumulatedReports = (req, res) => {
accumulated_data: data, accumulated_data: data,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (err) {
logger.error('누적 현황 조회 오류:', err);
res.status(500).json({
error: '누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
}); });
}
}; };
/** /**
@@ -870,7 +713,7 @@ const createFromTbm = async (req, res) => {
total_hours, total_hours,
error_hours: error_hours || 0, error_hours: error_hours || 0,
regular_hours, regular_hours,
work_status_id: work_status_id || (error_hours > 0 ? 2 : 1), // error_hours가 있으면 상태 2 (부적합) work_status_id: work_status_id || (error_hours > 0 ? 2 : 1),
error_type_id, error_type_id,
created_by: req.user.user_id created_by: req.user.user_id
}; };
@@ -884,31 +727,29 @@ const createFromTbm = async (req, res) => {
}); });
} catch (err) { } catch (err) {
console.error('TBM 작업보고서 생성 오류:', err); logger.error('TBM 작업보고서 생성 오류:', err);
console.error('Error stack:', err.stack);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'TBM 작업보고서 생성 중 오류가 발생했습니다.', message: 'TBM 작업보고서 생성 중 오류가 발생했습니다.',
error: err.message, error: err.message
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
}); });
} }
}; };
// 모든 컨트롤러 함수 내보내기 (리팩토링된 함수 위주로 재구성) // 모든 컨트롤러 함수 내보내기
module.exports = { module.exports = {
// 📝 V2 핵심 CRUD 함수 // V2 핵심 CRUD 함수
createDailyWorkReport, createDailyWorkReport,
getDailyWorkReports, getDailyWorkReports,
updateWorkReport, updateWorkReport,
removeDailyWorkReport, removeDailyWorkReport,
createFromTbm, createFromTbm,
// 📊 V2 통계 및 요약 함수 // V2 통계 및 요약 함수
getWorkReportStats, getWorkReportStats,
getDailySummary, getDailySummary,
// 🔽 아직 리팩토링되지 않은 레거시 함수들 // 레거시 함수 (콜백 제거 완료)
getAccumulatedReports, getAccumulatedReports,
getContributorsSummary, getContributorsSummary,
getMyAccumulatedData, getMyAccumulatedData,
@@ -921,7 +762,7 @@ module.exports = {
getWorkStatusTypes, getWorkStatusTypes,
getErrorTypes, getErrorTypes,
// 🔽 마스터 데이터 CRUD // 마스터 데이터 CRUD
createWorkType, createWorkType,
updateWorkType, updateWorkType,
deleteWorkType, deleteWorkType,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
const vacationBalanceModel = require('../models/vacationBalanceModel'); const vacationBalanceModel = require('../models/vacationBalanceModel');
const vacationTypeModel = require('../models/vacationTypeModel'); const vacationTypeModel = require('../models/vacationTypeModel');
const logger = require('../utils/logger');
const vacationBalanceController = { const vacationBalanceController = {
/** /**
@@ -14,27 +15,11 @@ const vacationBalanceController = {
async getByWorkerAndYear(req, res) { async getByWorkerAndYear(req, res) {
try { try {
const { workerId, year } = req.params; const { workerId, year } = req.params;
const results = await vacationBalanceModel.getByWorkerAndYear(workerId, year);
vacationBalanceModel.getByWorkerAndYear(workerId, year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('휴가 잔액 조회 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getByWorkerAndYear 오류:', error); logger.error('휴가 잔액 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '휴가 잔액을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -45,27 +30,11 @@ const vacationBalanceController = {
async getAllByYear(req, res) { async getAllByYear(req, res) {
try { try {
const { year } = req.params; const { year } = req.params;
const results = await vacationBalanceModel.getAllByYear(year);
vacationBalanceModel.getAllByYear(year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('전체 휴가 잔액 조회 오류:', err);
return res.status(500).json({
success: false,
message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAllByYear 오류:', error); logger.error('전체 휴가 잔액 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -75,17 +44,9 @@ const vacationBalanceController = {
*/ */
async createBalance(req, res) { async createBalance(req, res) {
try { try {
const { const { worker_id, vacation_type_id, year, total_days, used_days, notes } = req.body;
worker_id,
vacation_type_id,
year,
total_days,
used_days,
notes
} = req.body;
const created_by = req.user.user_id; const created_by = req.user.user_id;
// 필수 필드 검증
if (!worker_id || !vacation_type_id || !year || total_days === undefined) { if (!worker_id || !vacation_type_id || !year || total_days === undefined) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -94,15 +55,7 @@ const vacationBalanceController = {
} }
// 중복 체크 // 중복 체크
vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year, (err, existing) => { const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year);
if (err) {
console.error('중복 체크 오류:', err);
return res.status(500).json({
success: false,
message: '중복 체크 중 오류가 발생했습니다'
});
}
if (existing && existing.length > 0) { if (existing && existing.length > 0) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -120,28 +73,15 @@ const vacationBalanceController = {
created_by created_by
}; };
vacationBalanceModel.create(balanceData, (err, result) => { const result = await vacationBalanceModel.create(balanceData);
if (err) {
console.error('휴가 잔액 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '휴가 잔액이 생성되었습니다', message: '휴가 잔액이 생성되었습니다',
data: { id: result.insertId } data: { id: result.insertId }
}); });
});
});
} catch (error) { } catch (error) {
console.error('createBalance 오류:', error); logger.error('휴가 잔액 생성 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -161,39 +101,18 @@ const vacationBalanceController = {
updateData.updated_at = new Date(); updateData.updated_at = new Date();
if (Object.keys(updateData).length === 1) { if (Object.keys(updateData).length === 1) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
success: false,
message: '수정할 데이터가 없습니다'
});
}
vacationBalanceModel.update(id, updateData, (err, result) => {
if (err) {
console.error('휴가 잔액 수정 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 수정하는 중 오류가 발생했습니다'
});
} }
const result = await vacationBalanceModel.update(id, updateData);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
success: false,
message: '휴가 잔액을 찾을 수 없습니다'
});
} }
res.json({ res.json({ success: true, message: '휴가 잔액이 수정되었습니다' });
success: true,
message: '휴가 잔액이 수정되었습니다'
});
});
} catch (error) { } catch (error) {
console.error('updateBalance 오류:', error); logger.error('휴가 잔액 수정 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -204,34 +123,16 @@ const vacationBalanceController = {
async deleteBalance(req, res) { async deleteBalance(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const result = await vacationBalanceModel.delete(id);
vacationBalanceModel.delete(id, (err, result) => {
if (err) {
console.error('휴가 잔액 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 삭제하는 중 오류가 발생했습니다'
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
success: false,
message: '휴가 잔액을 찾을 수 없습니다'
});
} }
res.json({ res.json({ success: true, message: '휴가 잔액이 삭제되었습니다' });
success: true,
message: '휴가 잔액이 삭제되었습니다'
});
});
} catch (error) { } catch (error) {
console.error('deleteBalance 오류:', error); logger.error('휴가 잔액 삭제 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -251,31 +152,18 @@ const vacationBalanceController = {
}); });
} }
// 연차 일수 계산
const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year); const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year);
// ANNUAL 휴가 유형 ID 조회 // ANNUAL 휴가 유형 ID 조회
vacationTypeModel.getByCode('ANNUAL', (err, types) => { const types = await vacationTypeModel.getByCode('ANNUAL');
if (err || !types || types.length === 0) { if (!types || types.length === 0) {
console.error('ANNUAL 휴가 유형 조회 오류:', err); return res.status(500).json({ success: false, message: 'ANNUAL 휴가 유형을 찾을 수 없습니다' });
return res.status(500).json({
success: false,
message: 'ANNUAL 휴가 유형을 찾을 수 없습니다'
});
} }
const annualTypeId = types[0].id; const annualTypeId = types[0].id;
// 중복 체크 // 중복 체크
vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year, (err, existing) => { const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year);
if (err) {
console.error('중복 체크 오류:', err);
return res.status(500).json({
success: false,
message: '중복 체크 중 오류가 발생했습니다'
});
}
if (existing && existing.length > 0) { if (existing && existing.length > 0) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -293,32 +181,15 @@ const vacationBalanceController = {
created_by created_by
}; };
vacationBalanceModel.create(balanceData, (err, result) => { const result = await vacationBalanceModel.create(balanceData);
if (err) {
console.error('휴가 잔액 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: `${annualDays}일의 연차가 자동으로 생성되었습니다`, message: `${annualDays}일의 연차가 자동으로 생성되었습니다`,
data: { data: { id: result.insertId, calculated_days: annualDays }
id: result.insertId,
calculated_days: annualDays
}
});
});
});
}); });
} catch (error) { } catch (error) {
console.error('autoCalculateAndCreate 오류:', error); logger.error('연차 자동 계산 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -332,10 +203,7 @@ const vacationBalanceController = {
const created_by = req.user.user_id; const created_by = req.user.user_id;
if (!balances || !Array.isArray(balances) || balances.length === 0) { if (!balances || !Array.isArray(balances) || balances.length === 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '저장할 데이터가 없습니다' });
success: false,
message: '저장할 데이터가 없습니다'
});
} }
const { getDb } = require('../dbPool'); const { getDb } = require('../dbPool');
@@ -353,7 +221,6 @@ const vacationBalanceController = {
} }
try { try {
// Upsert 쿼리
const query = ` const query = `
INSERT INTO vacation_balance_details INSERT INTO vacation_balance_details
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
@@ -367,7 +234,7 @@ const vacationBalanceController = {
await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]); await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]);
successCount++; successCount++;
} catch (err) { } catch (err) {
console.error('휴가 잔액 저장 오류:', err); logger.error('휴가 잔액 저장 오류:', err);
errorCount++; errorCount++;
} }
} }
@@ -378,11 +245,8 @@ const vacationBalanceController = {
data: { successCount, errorCount } data: { successCount, errorCount }
}); });
} catch (error) { } catch (error) {
console.error('bulkUpsert 오류:', error); logger.error('bulkUpsert 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -393,27 +257,11 @@ const vacationBalanceController = {
async getAvailableDays(req, res) { async getAvailableDays(req, res) {
try { try {
const { workerId, year } = req.params; const { workerId, year } = req.params;
const results = await vacationBalanceModel.getAvailableVacationDays(workerId, year);
vacationBalanceModel.getAvailableVacationDays(workerId, year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('사용 가능 휴가 조회 오류:', err);
return res.status(500).json({
success: false,
message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAvailableDays 오류:', error); logger.error('사용 가능 휴가 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
} }
}; };

View File

@@ -4,6 +4,7 @@
*/ */
const vacationTypeModel = require('../models/vacationTypeModel'); const vacationTypeModel = require('../models/vacationTypeModel');
const logger = require('../utils/logger');
const vacationTypeController = { const vacationTypeController = {
/** /**
@@ -12,26 +13,11 @@ const vacationTypeController = {
*/ */
async getAllTypes(req, res) { async getAllTypes(req, res) {
try { try {
vacationTypeModel.getAll((err, results) => { const results = await vacationTypeModel.getAll();
if (err) { res.json({ success: true, data: results });
console.error('휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAllTypes 오류:', error); logger.error('휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -41,26 +27,11 @@ const vacationTypeController = {
*/ */
async getSystemTypes(req, res) { async getSystemTypes(req, res) {
try { try {
vacationTypeModel.getSystemTypes((err, results) => { const results = await vacationTypeModel.getSystemTypes();
if (err) { res.json({ success: true, data: results });
console.error('시스템 휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getSystemTypes 오류:', error); logger.error('시스템 휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -70,26 +41,11 @@ const vacationTypeController = {
*/ */
async getSpecialTypes(req, res) { async getSpecialTypes(req, res) {
try { try {
vacationTypeModel.getSpecialTypes((err, results) => { const results = await vacationTypeModel.getSpecialTypes();
if (err) { res.json({ success: true, data: results });
console.error('특별 휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getSpecialTypes 오류:', error); logger.error('특별 휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -99,15 +55,8 @@ const vacationTypeController = {
*/ */
async createType(req, res) { async createType(req, res) {
try { try {
const { const { type_code, type_name, deduct_days, priority, description } = req.body;
type_code,
type_name,
deduct_days,
priority,
description
} = req.body;
// 필수 필드 검증
if (!type_code || !type_name || !deduct_days) { if (!type_code || !type_name || !deduct_days) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -116,23 +65,11 @@ const vacationTypeController = {
} }
// type_code 중복 체크 // type_code 중복 체크
vacationTypeModel.getByCode(type_code, (err, existingTypes) => { const existingTypes = await vacationTypeModel.getByCode(type_code);
if (err) {
console.error('type_code 중복 체크 오류:', err);
return res.status(500).json({
success: false,
message: 'type_code 중복 체크 중 오류가 발생했습니다'
});
}
if (existingTypes && existingTypes.length > 0) { if (existingTypes && existingTypes.length > 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '이미 존재하는 type_code입니다' });
success: false,
message: '이미 존재하는 type_code입니다'
});
} }
// 특별 휴가 유형으로 생성
const typeData = { const typeData = {
type_code, type_code,
type_name, type_name,
@@ -144,28 +81,15 @@ const vacationTypeController = {
is_active: true is_active: true
}; };
vacationTypeModel.create(typeData, (err, result) => { const result = await vacationTypeModel.create(typeData);
if (err) {
console.error('휴가 유형 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '특별 휴가 유형이 생성되었습니다', message: '특별 휴가 유형이 생성되었습니다',
data: { id: result.insertId } data: { id: result.insertId }
}); });
});
});
} catch (error) { } catch (error) {
console.error('createType 오류:', error); logger.error('휴가 유형 생성 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -176,41 +100,20 @@ const vacationTypeController = {
async updateType(req, res) { async updateType(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const { const { type_name, deduct_days, priority, description, is_active } = req.body;
type_name,
deduct_days,
priority,
description,
is_active
} = req.body;
// 먼저 해당 유형 조회
vacationTypeModel.getById(id, (err, types) => {
if (err) {
console.error('휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
const types = await vacationTypeModel.getById(id);
if (!types || types.length === 0) { if (!types || types.length === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '휴가 유형을 찾을 수 없습니다' });
success: false,
message: '휴가 유형을 찾을 수 없습니다'
});
} }
const type = types[0]; const type = types[0];
// 시스템 기본 휴가의 경우 제한적으로만 수정 가능
const updateData = {}; const updateData = {};
if (type.is_system) { if (type.is_system) {
// 시스템 휴가는 priority와 description만 수정 가능
if (priority !== undefined) updateData.priority = priority; if (priority !== undefined) updateData.priority = priority;
if (description !== undefined) updateData.description = description; if (description !== undefined) updateData.description = description;
} else { } else {
// 특별 휴가는 모든 필드 수정 가능
if (type_name) updateData.type_name = type_name; if (type_name) updateData.type_name = type_name;
if (deduct_days !== undefined) updateData.deduct_days = deduct_days; if (deduct_days !== undefined) updateData.deduct_days = deduct_days;
if (priority !== undefined) updateData.priority = priority; if (priority !== undefined) updateData.priority = priority;
@@ -219,35 +122,15 @@ const vacationTypeController = {
} }
if (Object.keys(updateData).length === 0) { if (Object.keys(updateData).length === 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
success: false,
message: '수정할 데이터가 없습니다'
});
} }
updateData.updated_at = new Date(); updateData.updated_at = new Date();
await vacationTypeModel.update(id, updateData);
vacationTypeModel.update(id, updateData, (err, result) => { res.json({ success: true, message: '휴가 유형이 수정되었습니다' });
if (err) {
console.error('휴가 유형 수정 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 수정하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
message: '휴가 유형이 수정되었습니다'
});
});
});
} catch (error) { } catch (error) {
console.error('updateType 오류:', error); logger.error('휴가 유형 수정 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -258,15 +141,7 @@ const vacationTypeController = {
async deleteType(req, res) { async deleteType(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const result = await vacationTypeModel.delete(id);
vacationTypeModel.delete(id, (err, result) => {
if (err) {
console.error('휴가 유형 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 삭제하는 중 오류가 발생했습니다'
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(400).json({ return res.status(400).json({
@@ -275,17 +150,10 @@ const vacationTypeController = {
}); });
} }
res.json({ res.json({ success: true, message: '휴가 유형이 삭제되었습니다' });
success: true,
message: '휴가 유형이 삭제되었습니다'
});
});
} catch (error) { } catch (error) {
console.error('deleteType 오류:', error); logger.error('휴가 유형 삭제 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -297,35 +165,22 @@ const vacationTypeController = {
try { try {
const { priorities } = req.body; const { priorities } = req.body;
// priorities = [{ id: 1, priority: 10 }, { id: 2, priority: 20 }, ...]
if (!priorities || !Array.isArray(priorities)) { if (!priorities || !Array.isArray(priorities)) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'priorities 배열이 필요합니다' });
success: false,
message: 'priorities 배열이 필요합니다'
});
} }
vacationTypeModel.updatePriorities(priorities, (err, result) => { for (const { id, priority } of priorities) {
if (err) { await vacationTypeModel.updatePriority(id, priority);
console.error('우선순위 업데이트 오류:', err);
return res.status(500).json({
success: false,
message: '우선순위를 업데이트하는 중 오류가 발생했습니다'
});
} }
res.json({ res.json({
success: true, success: true,
message: '우선순위가 업데이트되었습니다', message: '우선순위가 업데이트되었습니다',
data: { updated: result.affectedRows } data: { updated: priorities.length }
});
}); });
} catch (error) { } catch (error) {
console.error('updatePriorities 오류:', error); logger.error('우선순위 업데이트 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
} }
}; };

View File

@@ -1,50 +1,34 @@
const visitRequestModel = require('../models/visitRequestModel'); const visitRequestModel = require('../models/visitRequestModel');
const logger = require('../utils/logger');
// ==================== 출입 신청 관리 ==================== // ==================== 출입 신청 관리 ====================
/** exports.createVisitRequest = async (req, res) => {
* 출입 신청 생성 try {
*/
exports.createVisitRequest = (req, res) => {
const requester_id = req.user.user_id; const requester_id = req.user.user_id;
const requestData = { const requestData = { requester_id, ...req.body };
requester_id,
...req.body
};
// 필수 필드 검증
const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id']; const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id'];
for (const field of requiredFields) { for (const field of requiredFields) {
if (!requestData[field]) { if (!requestData[field]) {
return res.status(400).json({ return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
success: false,
message: `${field}는 필수 입력 항목입니다.`
});
} }
} }
visitRequestModel.createVisitRequest(requestData, (err, requestId) => { const requestId = await visitRequestModel.createVisitRequest(requestData);
if (err) {
console.error('출입 신청 생성 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 생성 중 오류가 발생했습니다.',
error: err.message
});
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '출입 신청이 성공적으로 생성되었습니다.', message: '출입 신청이 성공적으로 생성되었습니다.',
data: { request_id: requestId } data: { request_id: requestId }
}); });
}); } catch (err) {
logger.error('출입 신청 생성 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 생성 중 오류가 발생했습니다.' });
}
}; };
/** exports.getAllVisitRequests = async (req, res) => {
* 출입 신청 목록 조회 try {
*/
exports.getAllVisitRequests = (req, res) => {
const filters = { const filters = {
status: req.query.status, status: req.query.status,
visit_date: req.query.visit_date, visit_date: req.query.visit_date,
@@ -54,359 +38,168 @@ exports.getAllVisitRequests = (req, res) => {
category_id: req.query.category_id category_id: req.query.category_id
}; };
visitRequestModel.getAllVisitRequests(filters, (err, requests) => { const requests = await visitRequestModel.getAllVisitRequests(filters);
if (err) { res.json({ success: true, data: requests });
console.error('출입 신청 목록 조회 오류:', err); } catch (err) {
return res.status(500).json({ logger.error('출입 신청 목록 조회 오류:', err);
success: false, res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' });
message: '출입 신청 목록 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({
success: true,
data: requests
});
});
}; };
/** exports.getVisitRequestById = async (req, res) => {
* 출입 신청 상세 조회 try {
*/ const request = await visitRequestModel.getVisitRequestById(req.params.id);
exports.getVisitRequestById = (req, res) => {
const requestId = req.params.id;
visitRequestModel.getVisitRequestById(requestId, (err, request) => {
if (err) {
console.error('출입 신청 조회 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 조회 중 오류가 발생했습니다.',
error: err.message
});
}
if (!request) { if (!request) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false, }
message: '출입 신청을 찾을 수 없습니다.' res.json({ success: true, data: request });
}); } catch (err) {
logger.error('출입 신청 조회 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 조회 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
data: request
});
});
}; };
/** exports.updateVisitRequest = async (req, res) => {
* 출입 신청 수정 try {
*/ const result = await visitRequestModel.updateVisitRequest(req.params.id, req.body);
exports.updateVisitRequest = (req, res) => {
const requestId = req.params.id;
const requestData = req.body;
visitRequestModel.updateVisitRequest(requestId, requestData, (err, result) => {
if (err) {
console.error('출입 신청 수정 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false, }
message: '출입 신청을 찾을 수 없습니다.' res.json({ success: true, message: '출입 신청이 수정되었습니다.' });
}); } catch (err) {
logger.error('출입 신청 수정 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 수정 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '출입 신청이 수정되었습니다.'
});
});
}; };
/** exports.deleteVisitRequest = async (req, res) => {
* 출입 신청 삭제 try {
*/ const result = await visitRequestModel.deleteVisitRequest(req.params.id);
exports.deleteVisitRequest = (req, res) => {
const requestId = req.params.id;
visitRequestModel.deleteVisitRequest(requestId, (err, result) => {
if (err) {
console.error('출입 신청 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 삭제 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false, }
message: '출입 신청을 찾을 수 없습니다.' res.json({ success: true, message: '출입 신청이 삭제되었습니다.' });
}); } catch (err) {
logger.error('출입 신청 삭제 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 삭제 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '출입 신청이 삭제되었습니다.'
});
});
}; };
/** exports.approveVisitRequest = async (req, res) => {
* 출입 신청 승인 try {
*/ const result = await visitRequestModel.approveVisitRequest(req.params.id, req.user.user_id);
exports.approveVisitRequest = (req, res) => {
const requestId = req.params.id;
const approvedBy = req.user.user_id;
visitRequestModel.approveVisitRequest(requestId, approvedBy, (err, result) => {
if (err) {
console.error('출입 신청 승인 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 승인 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false, }
message: '출입 신청을 찾을 수 없습니다.' res.json({ success: true, message: '출입 신청이 승인되었습니다.' });
}); } catch (err) {
logger.error('출입 신청 승인 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 승인 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '출입 신청이 승인되었습니다.'
});
});
}; };
/** exports.rejectVisitRequest = async (req, res) => {
* 출입 신청 반려 try {
*/
exports.rejectVisitRequest = (req, res) => {
const requestId = req.params.id;
const approvedBy = req.user.user_id;
const rejectionReason = req.body.rejection_reason || '사유 없음';
const rejectionData = { const rejectionData = {
approved_by: approvedBy, approved_by: req.user.user_id,
rejection_reason: rejectionReason rejection_reason: req.body.rejection_reason || '사유 없음'
}; };
const result = await visitRequestModel.rejectVisitRequest(req.params.id, rejectionData);
visitRequestModel.rejectVisitRequest(requestId, rejectionData, (err, result) => {
if (err) {
console.error('출입 신청 반려 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 반려 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false, }
message: '출입 신청을 찾을 수 없습니다.' res.json({ success: true, message: '출입 신청이 반려되었습니다.' });
}); } catch (err) {
logger.error('출입 신청 반려 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 반려 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '출입 신청이 반려되었습니다.'
});
});
}; };
// ==================== 방문 목적 관리 ==================== // ==================== 방문 목적 관리 ====================
/** exports.getAllVisitPurposes = async (req, res) => {
* 모든 방문 목적 조회 try {
*/ const purposes = await visitRequestModel.getAllVisitPurposes();
exports.getAllVisitPurposes = (req, res) => { res.json({ success: true, data: purposes });
visitRequestModel.getAllVisitPurposes((err, purposes) => { } catch (err) {
if (err) { logger.error('방문 목적 조회 오류:', err);
console.error('방문 목적 조회 오류:', err); res.status(500).json({ success: false, message: '방문 목적 조회 중 오류가 발생했습니다.' });
return res.status(500).json({
success: false,
message: '방문 목적 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({
success: true,
data: purposes
});
});
}; };
/** exports.getActiveVisitPurposes = async (req, res) => {
* 활성 방문 목적만 조회 try {
*/ const purposes = await visitRequestModel.getActiveVisitPurposes();
exports.getActiveVisitPurposes = (req, res) => { res.json({ success: true, data: purposes });
visitRequestModel.getActiveVisitPurposes((err, purposes) => { } catch (err) {
if (err) { logger.error('활성 방문 목적 조회 오류:', err);
console.error('활성 방문 목적 조회 오류:', err); res.status(500).json({ success: false, message: '활성 방문 목적 조회 중 오류가 발생했습니다.' });
return res.status(500).json({
success: false,
message: '활성 방문 목적 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({
success: true,
data: purposes
});
});
}; };
/** exports.createVisitPurpose = async (req, res) => {
* 방문 목적 추가 try {
*/ if (!req.body.purpose_name) {
exports.createVisitPurpose = (req, res) => { return res.status(400).json({ success: false, message: 'purpose_name은 필수 입력 항목입니다.' });
const purposeData = req.body;
if (!purposeData.purpose_name) {
return res.status(400).json({
success: false,
message: 'purpose_name은 필수 입력 항목입니다.'
});
} }
const purposeId = await visitRequestModel.createVisitPurpose(req.body);
visitRequestModel.createVisitPurpose(purposeData, (err, purposeId) => {
if (err) {
console.error('방문 목적 추가 오류:', err);
return res.status(500).json({
success: false,
message: '방문 목적 추가 중 오류가 발생했습니다.',
error: err.message
});
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '방문 목적이 추가되었습니다.', message: '방문 목적이 추가되었습니다.',
data: { purpose_id: purposeId } data: { purpose_id: purposeId }
}); });
}); } catch (err) {
logger.error('방문 목적 추가 오류:', err);
res.status(500).json({ success: false, message: '방문 목적 추가 중 오류가 발생했습니다.' });
}
}; };
/** exports.updateVisitPurpose = async (req, res) => {
* 방문 목적 수정 try {
*/ const result = await visitRequestModel.updateVisitPurpose(req.params.id, req.body);
exports.updateVisitPurpose = (req, res) => {
const purposeId = req.params.id;
const purposeData = req.body;
visitRequestModel.updateVisitPurpose(purposeId, purposeData, (err, result) => {
if (err) {
console.error('방문 목적 수정 오류:', err);
return res.status(500).json({
success: false,
message: '방문 목적 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
success: false, }
message: '방문 목적을 찾을 수 없습니다.' res.json({ success: true, message: '방문 목적이 수정되었습니다.' });
}); } catch (err) {
logger.error('방문 목적 수정 오류:', err);
res.status(500).json({ success: false, message: '방문 목적 수정 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '방문 목적이 수정되었습니다.'
});
});
}; };
/** exports.deleteVisitPurpose = async (req, res) => {
* 방문 목적 삭제 try {
*/ const result = await visitRequestModel.deleteVisitPurpose(req.params.id);
exports.deleteVisitPurpose = (req, res) => {
const purposeId = req.params.id;
visitRequestModel.deleteVisitPurpose(purposeId, (err, result) => {
if (err) {
console.error('방문 목적 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '방문 목적 삭제 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
success: false, }
message: '방문 목적을 찾을 수 없습니다.' res.json({ success: true, message: '방문 목적이 삭제되었습니다.' });
}); } catch (err) {
logger.error('방문 목적 삭제 오류:', err);
res.status(500).json({ success: false, message: '방문 목적 삭제 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '방문 목적이 삭제되었습니다.'
});
});
}; };
// ==================== 안전교육 기록 관리 ==================== // ==================== 안전교육 기록 관리 ====================
/** exports.createTrainingRecord = async (req, res) => {
* 안전교육 기록 생성 try {
*/ const trainingData = { trainer_id: req.user.user_id, ...req.body };
exports.createTrainingRecord = (req, res) => {
const trainerId = req.user.user_id;
const trainingData = {
trainer_id: trainerId,
...req.body
};
// 필수 필드 검증
const requiredFields = ['request_id', 'training_date', 'training_start_time']; const requiredFields = ['request_id', 'training_date', 'training_start_time'];
for (const field of requiredFields) { for (const field of requiredFields) {
if (!trainingData[field]) { if (!trainingData[field]) {
return res.status(400).json({ return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
success: false,
message: `${field}는 필수 입력 항목입니다.`
});
} }
} }
visitRequestModel.createTrainingRecord(trainingData, (err, trainingId) => { const trainingId = await visitRequestModel.createTrainingRecord(trainingData);
if (err) {
console.error('안전교육 기록 생성 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 생성 중 오류가 발생했습니다.',
error: err.message
});
}
// 안전교육 기록 생성되면 출입 신청 상태를 training_completed로 변경 // 안전교육 기록 생성 출입 신청 상태를 training_completed로 변경
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태를 training_completed로 변경 중...`); try {
visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed', (statusErr) => { await visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed');
if (statusErr) { } catch (statusErr) {
console.error('출입 신청 상태 업데이트 오류:', statusErr); logger.error('출입 신청 상태 업데이트 오류:', statusErr);
// 에러가 발생해도 교육 기록은 생성되었으므로 성공 응답
} else {
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태 변경 성공`);
} }
res.status(201).json({ res.status(201).json({
@@ -414,142 +207,78 @@ exports.createTrainingRecord = (req, res) => {
message: '안전교육 기록이 생성되었습니다.', message: '안전교육 기록이 생성되었습니다.',
data: { training_id: trainingId } data: { training_id: trainingId }
}); });
}); } catch (err) {
}); logger.error('안전교육 기록 생성 오류:', err);
res.status(500).json({ success: false, message: '안전교육 기록 생성 중 오류가 발생했습니다.' });
}
}; };
/** exports.getTrainingRecordByRequestId = async (req, res) => {
* 특정 출입 신청의 안전교육 기록 조회 try {
*/ const record = await visitRequestModel.getTrainingRecordByRequestId(req.params.requestId);
exports.getTrainingRecordByRequestId = (req, res) => { res.json({ success: true, data: record || null });
const requestId = req.params.requestId; } catch (err) {
logger.error('안전교육 기록 조회 오류:', err);
visitRequestModel.getTrainingRecordByRequestId(requestId, (err, record) => { res.status(500).json({ success: false, message: '안전교육 기록 조회 중 오류가 발생했습니다.' });
if (err) {
console.error('안전교육 기록 조회 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({
success: true,
data: record || null
});
});
}; };
/** exports.updateTrainingRecord = async (req, res) => {
* 안전교육 기록 수정 try {
*/ const result = await visitRequestModel.updateTrainingRecord(req.params.id, req.body);
exports.updateTrainingRecord = (req, res) => {
const trainingId = req.params.id;
const trainingData = req.body;
visitRequestModel.updateTrainingRecord(trainingId, trainingData, (err, result) => {
if (err) {
console.error('안전교육 기록 수정 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
success: false, }
message: '안전교육 기록을 찾을 수 없습니다.' res.json({ success: true, message: '안전교육 기록이 수정되었습니다.' });
}); } catch (err) {
logger.error('안전교육 기록 수정 오류:', err);
res.status(500).json({ success: false, message: '안전교육 기록 수정 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '안전교육 기록이 수정되었습니다.'
});
});
}; };
/** exports.completeTraining = async (req, res) => {
* 안전교육 완료 (서명 포함) try {
*/
exports.completeTraining = (req, res) => {
const trainingId = req.params.id; const trainingId = req.params.id;
const signatureData = req.body.signature_data; const signatureData = req.body.signature_data;
if (!signatureData) { if (!signatureData) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '서명 데이터가 필요합니다.' });
success: false,
message: '서명 데이터가 필요합니다.'
});
}
visitRequestModel.completeTraining(trainingId, signatureData, (err, result) => {
if (err) {
console.error('안전교육 완료 처리 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 완료 처리 중 오류가 발생했습니다.',
error: err.message
});
} }
const result = await visitRequestModel.completeTraining(trainingId, signatureData);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
success: false,
message: '안전교육 기록을 찾을 수 없습니다.'
});
} }
// 교육 완료 후 출입 신청 상태를 'training_completed'로 변경 // 교육 완료 후 출입 신청 상태 변경
visitRequestModel.getTrainingRecordByRequestId(trainingId, (err, record) => { try {
if (err || !record) { const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId);
return res.json({ if (record) {
success: true, await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed');
message: '안전교육이 완료되었습니다.' }
}); } catch (statusErr) {
logger.error('출입 신청 상태 업데이트 오류:', statusErr);
} }
visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed', (err) => { res.json({ success: true, message: '안전교육이 완료되었습니다.' });
if (err) { } catch (err) {
console.error('출입 신청 상태 업데이트 오류:', err); logger.error('안전교육 완료 처리 오류:', err);
res.status(500).json({ success: false, message: '안전교육 완료 처리 중 오류가 발생했습니다.' });
} }
res.json({
success: true,
message: '안전교육이 완료되었습니다.'
});
});
});
});
}; };
/** exports.getTrainingRecords = async (req, res) => {
* 안전교육 기록 목록 조회 try {
*/
exports.getTrainingRecords = (req, res) => {
const filters = { const filters = {
training_date: req.query.training_date, training_date: req.query.training_date,
start_date: req.query.start_date, start_date: req.query.start_date,
end_date: req.query.end_date, end_date: req.query.end_date,
trainer_id: req.query.trainer_id trainer_id: req.query.trainer_id
}; };
const records = await visitRequestModel.getTrainingRecords(filters);
visitRequestModel.getTrainingRecords(filters, (err, records) => { res.json({ success: true, data: records });
if (err) { } catch (err) {
console.error('안전교육 기록 목록 조회 오류:', err); logger.error('안전교육 기록 목록 조회 오류:', err);
return res.status(500).json({ res.status(500).json({ success: false, message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' });
success: false,
message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({
success: true,
data: records
});
});
}; };

View File

@@ -4,198 +4,149 @@
const workIssueModel = require('../models/workIssueModel'); const workIssueModel = require('../models/workIssueModel');
const imageUploadService = require('../services/imageUploadService'); const imageUploadService = require('../services/imageUploadService');
const logger = require('../utils/logger');
// ==================== 신고 카테고리 관리 ==================== // ==================== 신고 카테고리 관리 ====================
/** exports.getAllCategories = async (req, res) => {
* 모든 카테고리 조회 try {
*/ const categories = await workIssueModel.getAllCategories();
exports.getAllCategories = (req, res) => {
workIssueModel.getAllCategories((err, categories) => {
if (err) {
console.error('카테고리 조회 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
res.json({ success: true, data: categories }); res.json({ success: true, data: categories });
}); } catch (err) {
logger.error('카테고리 조회 실패:', err);
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
}; };
/** exports.getCategoriesByType = async (req, res) => {
* 타입별 카테고리 조회 try {
*/
exports.getCategoriesByType = (req, res) => {
const { type } = req.params; const { type } = req.params;
if (!['nonconformity', 'safety', 'facility'].includes(type)) { if (!['nonconformity', 'safety', 'facility'].includes(type)) {
return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' }); return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' });
} }
workIssueModel.getCategoriesByType(type, (err, categories) => { const categories = await workIssueModel.getCategoriesByType(type);
if (err) {
console.error('카테고리 조회 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
res.json({ success: true, data: categories }); res.json({ success: true, data: categories });
}); } catch (err) {
logger.error('카테고리 조회 실패:', err);
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
}; };
/** exports.createCategory = async (req, res) => {
* 카테고리 생성 try {
*/
exports.createCategory = (req, res) => {
const { category_type, category_name, description, display_order } = req.body; const { category_type, category_name, description, display_order } = req.body;
if (!category_type || !category_name) { if (!category_type || !category_name) {
return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' }); return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' });
} }
workIssueModel.createCategory( const categoryId = await workIssueModel.createCategory({ category_type, category_name, description, display_order });
{ category_type, category_name, description, display_order },
(err, categoryId) => {
if (err) {
console.error('카테고리 생성 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 생성 실패' });
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '카테고리가 생성되었습니다.', message: '카테고리가 생성되었습니다.',
data: { category_id: categoryId } data: { category_id: categoryId }
}); });
} catch (err) {
logger.error('카테고리 생성 실패:', err);
res.status(500).json({ success: false, error: '카테고리 생성 실패' });
} }
);
}; };
/** exports.updateCategory = async (req, res) => {
* 카테고리 수정 try {
*/
exports.updateCategory = (req, res) => {
const { id } = req.params; const { id } = req.params;
const { category_name, description, display_order, is_active } = req.body; const { category_name, description, display_order, is_active } = req.body;
workIssueModel.updateCategory( await workIssueModel.updateCategory(id, { category_name, description, display_order, is_active });
id,
{ category_name, description, display_order, is_active },
(err, result) => {
if (err) {
console.error('카테고리 수정 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 수정 실패' });
}
res.json({ success: true, message: '카테고리가 수정되었습니다.' }); res.json({ success: true, message: '카테고리가 수정되었습니다.' });
} catch (err) {
logger.error('카테고리 수정 실패:', err);
res.status(500).json({ success: false, error: '카테고리 수정 실패' });
} }
);
}; };
/** exports.deleteCategory = async (req, res) => {
* 카테고리 삭제 try {
*/
exports.deleteCategory = (req, res) => {
const { id } = req.params; const { id } = req.params;
await workIssueModel.deleteCategory(id);
workIssueModel.deleteCategory(id, (err, result) => {
if (err) {
console.error('카테고리 삭제 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
}
res.json({ success: true, message: '카테고리가 삭제되었습니다.' }); res.json({ success: true, message: '카테고리가 삭제되었습니다.' });
}); } catch (err) {
logger.error('카테고리 삭제 실패:', err);
res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
}
}; };
// ==================== 사전 정의 항목 관리 ==================== // ==================== 사전 정의 항목 관리 ====================
/** exports.getItemsByCategory = async (req, res) => {
* 카테고리별 항목 조회 try {
*/
exports.getItemsByCategory = (req, res) => {
const { categoryId } = req.params; const { categoryId } = req.params;
const items = await workIssueModel.getItemsByCategory(categoryId);
workIssueModel.getItemsByCategory(categoryId, (err, items) => {
if (err) {
console.error('항목 조회 실패:', err);
return res.status(500).json({ success: false, error: '항목 조회 실패' });
}
res.json({ success: true, data: items }); res.json({ success: true, data: items });
}); } catch (err) {
logger.error('항목 조회 실패:', err);
res.status(500).json({ success: false, error: '항목 조회 실패' });
}
}; };
/** exports.getAllItems = async (req, res) => {
* 모든 항목 조회 try {
*/ const items = await workIssueModel.getAllItems();
exports.getAllItems = (req, res) => {
workIssueModel.getAllItems((err, items) => {
if (err) {
console.error('항목 조회 실패:', err);
return res.status(500).json({ success: false, error: '항목 조회 실패' });
}
res.json({ success: true, data: items }); res.json({ success: true, data: items });
}); } catch (err) {
logger.error('항목 조회 실패:', err);
res.status(500).json({ success: false, error: '항목 조회 실패' });
}
}; };
/** exports.createItem = async (req, res) => {
* 항목 생성 try {
*/
exports.createItem = (req, res) => {
const { category_id, item_name, description, severity, display_order } = req.body; const { category_id, item_name, description, severity, display_order } = req.body;
if (!category_id || !item_name) { if (!category_id || !item_name) {
return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' }); return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' });
} }
workIssueModel.createItem( const itemId = await workIssueModel.createItem({ category_id, item_name, description, severity, display_order });
{ category_id, item_name, description, severity, display_order },
(err, itemId) => {
if (err) {
console.error('항목 생성 실패:', err);
return res.status(500).json({ success: false, error: '항목 생성 실패' });
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '항목이 생성되었습니다.', message: '항목이 생성되었습니다.',
data: { item_id: itemId } data: { item_id: itemId }
}); });
} catch (err) {
logger.error('항목 생성 실패:', err);
res.status(500).json({ success: false, error: '항목 생성 실패' });
} }
);
}; };
/** exports.updateItem = async (req, res) => {
* 항목 수정 try {
*/
exports.updateItem = (req, res) => {
const { id } = req.params; const { id } = req.params;
const { item_name, description, severity, display_order, is_active } = req.body; const { item_name, description, severity, display_order, is_active } = req.body;
workIssueModel.updateItem( await workIssueModel.updateItem(id, { item_name, description, severity, display_order, is_active });
id,
{ item_name, description, severity, display_order, is_active },
(err, result) => {
if (err) {
console.error('항목 수정 실패:', err);
return res.status(500).json({ success: false, error: '항목 수정 실패' });
}
res.json({ success: true, message: '항목이 수정되었습니다.' }); res.json({ success: true, message: '항목이 수정되었습니다.' });
} catch (err) {
logger.error('항목 수정 실패:', err);
res.status(500).json({ success: false, error: '항목 수정 실패' });
} }
);
}; };
/** exports.deleteItem = async (req, res) => {
* 항목 삭제 try {
*/
exports.deleteItem = (req, res) => {
const { id } = req.params; const { id } = req.params;
await workIssueModel.deleteItem(id);
workIssueModel.deleteItem(id, (err, result) => {
if (err) {
console.error('항목 삭제 실패:', err);
return res.status(500).json({ success: false, error: '항목 삭제 실패' });
}
res.json({ success: true, message: '항목이 삭제되었습니다.' }); res.json({ success: true, message: '항목이 삭제되었습니다.' });
}); } catch (err) {
logger.error('항목 삭제 실패:', err);
res.status(500).json({ success: false, error: '항목 삭제 실패' });
}
}; };
// ==================== 문제 신고 관리 ==================== // ==================== 문제 신고 관리 ====================
/**
* 신고 생성
*/
exports.createReport = async (req, res) => { exports.createReport = async (req, res) => {
try { try {
const { const {
@@ -206,7 +157,7 @@ exports.createReport = async (req, res) => {
visit_request_id, visit_request_id,
issue_category_id, issue_category_id,
issue_item_id, issue_item_id,
custom_item_name, // 직접 입력한 항목명 custom_item_name,
additional_description, additional_description,
photos = [] photos = []
} = req.body; } = req.body;
@@ -217,42 +168,25 @@ exports.createReport = async (req, res) => {
return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' }); return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' });
} }
// 위치 정보 검증 (지도 선택 또는 기타 위치)
if (!factory_category_id && !custom_location) { if (!factory_category_id && !custom_location) {
return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' }); return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' });
} }
// 항목 검증 (기존 항목 또는 직접 입력)
if (!issue_item_id && !custom_item_name) { if (!issue_item_id && !custom_item_name) {
return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' }); return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' });
} }
// 직접 입력한 항목이 있으면 DB에 저장
let finalItemId = issue_item_id; let finalItemId = issue_item_id;
if (custom_item_name && !issue_item_id) { if (custom_item_name && !issue_item_id) {
try { finalItemId = await workIssueModel.createItem({
finalItemId = await new Promise((resolve, reject) => {
workIssueModel.createItem(
{
category_id: issue_category_id, category_id: issue_category_id,
item_name: custom_item_name, item_name: custom_item_name,
description: '사용자 직접 입력', description: '사용자 직접 입력',
severity: 'medium', severity: 'medium',
display_order: 999 // 마지막에 표시 display_order: 999
},
(err, itemId) => {
if (err) reject(err);
else resolve(itemId);
}
);
}); });
} catch (itemErr) {
console.error('커스텀 항목 생성 실패:', itemErr);
return res.status(500).json({ success: false, error: '항목 저장 실패' });
}
} }
// 사진 저장 (최대 5장)
const photoPaths = { const photoPaths = {
photo_path1: null, photo_path1: null,
photo_path2: null, photo_path2: null,
@@ -283,27 +217,20 @@ exports.createReport = async (req, res) => {
...photoPaths ...photoPaths
}; };
workIssueModel.createReport(reportData, (err, reportId) => { const reportId = await workIssueModel.createReport(reportData);
if (err) {
console.error('신고 생성 실패:', err);
return res.status(500).json({ success: false, error: '신고 생성 실패' });
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '문제 신고가 등록되었습니다.', message: '문제 신고가 등록되었습니다.',
data: { report_id: reportId } data: { report_id: reportId }
}); });
}); } catch (err) {
} catch (error) { logger.error('신고 생성 실패:', err);
console.error('신고 생성 에러:', error);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
} }
}; };
/** exports.getAllReports = async (req, res) => {
* 신고 목록 조회 try {
*/
exports.getAllReports = (req, res) => {
const filters = { const filters = {
status: req.query.status, status: req.query.status,
category_type: req.query.category_type, category_type: req.query.category_type,
@@ -318,38 +245,28 @@ exports.getAllReports = (req, res) => {
offset: req.query.offset offset: req.query.offset
}; };
// 일반 사용자는 자신의 신고만 조회 (관리자 제외)
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
if (!['admin', 'system', 'support_team'].includes(userLevel)) { if (!['admin', 'system', 'support_team'].includes(userLevel)) {
filters.reporter_id = req.user.user_id; filters.reporter_id = req.user.user_id;
} }
workIssueModel.getAllReports(filters, (err, reports) => { const reports = await workIssueModel.getAllReports(filters);
if (err) {
console.error('신고 목록 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
}
res.json({ success: true, data: reports }); res.json({ success: true, data: reports });
}); } catch (err) {
logger.error('신고 목록 조회 실패:', err);
res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
}
}; };
/** exports.getReportById = async (req, res) => {
* 신고 상세 조회 try {
*/
exports.getReportById = (req, res) => {
const { id } = req.params; const { id } = req.params;
const report = await workIssueModel.getReportById(id);
workIssueModel.getReportById(id, (err, report) => {
if (err) {
console.error('신고 상세 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
}
if (!report) { if (!report) {
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' }); return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} }
// 권한 확인: 본인, 담당자, 또는 관리자
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; const isOwner = report.reporter_id === req.user.user_id;
const isAssignee = report.assigned_user_id === req.user.user_id; const isAssignee = report.assigned_user_id === req.user.user_id;
@@ -360,28 +277,21 @@ exports.getReportById = (req, res) => {
} }
res.json({ success: true, data: report }); res.json({ success: true, data: report });
}); } catch (err) {
logger.error('신고 상세 조회 실패:', err);
res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
}
}; };
/**
* 신고 수정
*/
exports.updateReport = async (req, res) => { exports.updateReport = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
// 기존 신고 확인 const report = await workIssueModel.getReportById(id);
workIssueModel.getReportById(id, async (err, report) => {
if (err) {
console.error('신고 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 조회 실패' });
}
if (!report) { if (!report) {
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' }); return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} }
// 권한 확인
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; const isOwner = report.reporter_id === req.user.user_id;
const isManager = ['admin', 'system'].includes(userLevel); const isManager = ['admin', 'system'].includes(userLevel);
@@ -390,7 +300,6 @@ exports.updateReport = async (req, res) => {
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' }); return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' });
} }
// 상태 확인: reported 상태에서만 수정 가능 (관리자 제외)
if (!isManager && report.status !== 'reported') { if (!isManager && report.status !== 'reported') {
return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' }); return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' });
} }
@@ -405,16 +314,13 @@ exports.updateReport = async (req, res) => {
photos = [] photos = []
} = req.body; } = req.body;
// 사진 업데이트 처리
const photoPaths = {}; const photoPaths = {};
for (let i = 0; i < Math.min(photos.length, 5); i++) { for (let i = 0; i < Math.min(photos.length, 5); i++) {
if (photos[i]) { if (photos[i]) {
// 기존 사진 삭제
const oldPath = report[`photo_path${i + 1}`]; const oldPath = report[`photo_path${i + 1}`];
if (oldPath) { if (oldPath) {
await imageUploadService.deleteFile(oldPath); await imageUploadService.deleteFile(oldPath);
} }
// 새 사진 저장
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue'); const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue');
if (savedPath) { if (savedPath) {
photoPaths[`photo_path${i + 1}`] = savedPath; photoPaths[`photo_path${i + 1}`] = savedPath;
@@ -432,37 +338,23 @@ exports.updateReport = async (req, res) => {
...photoPaths ...photoPaths
}; };
workIssueModel.updateReport(id, updateData, req.user.user_id, (updateErr, result) => { await workIssueModel.updateReport(id, updateData, req.user.user_id);
if (updateErr) {
console.error('신고 수정 실패:', updateErr);
return res.status(500).json({ success: false, error: '신고 수정 실패' });
}
res.json({ success: true, message: '신고가 수정되었습니다.' }); res.json({ success: true, message: '신고가 수정되었습니다.' });
}); } catch (err) {
}); logger.error('신고 수정 실패:', err);
} catch (error) {
console.error('신고 수정 에러:', error);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
} }
}; };
/**
* 신고 삭제
*/
exports.deleteReport = async (req, res) => { exports.deleteReport = async (req, res) => {
try {
const { id } = req.params; const { id } = req.params;
workIssueModel.getReportById(id, async (err, report) => { const report = await workIssueModel.getReportById(id);
if (err) {
console.error('신고 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 조회 실패' });
}
if (!report) { if (!report) {
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' }); return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} }
// 권한 확인
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; const isOwner = report.reporter_id === req.user.user_id;
const isManager = ['admin', 'system'].includes(userLevel); const isManager = ['admin', 'system'].includes(userLevel);
@@ -471,13 +363,8 @@ exports.deleteReport = async (req, res) => {
return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' }); return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' });
} }
workIssueModel.deleteReport(id, async (deleteErr, { result, photos }) => { const { photos } = await workIssueModel.deleteReport(id);
if (deleteErr) {
console.error('신고 삭제 실패:', deleteErr);
return res.status(500).json({ success: false, error: '신고 삭제 실패' });
}
// 사진 파일 삭제
if (photos) { if (photos) {
const allPhotos = [ const allPhotos = [
photos.photo_path1, photos.photo_path2, photos.photo_path3, photos.photo_path1, photos.photo_path2, photos.photo_path3,
@@ -488,31 +375,27 @@ exports.deleteReport = async (req, res) => {
} }
res.json({ success: true, message: '신고가 삭제되었습니다.' }); res.json({ success: true, message: '신고가 삭제되었습니다.' });
}); } catch (err) {
}); logger.error('신고 삭제 실패:', err);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
}
}; };
// ==================== 상태 관리 ==================== // ==================== 상태 관리 ====================
/** exports.receiveReport = async (req, res) => {
* 신고 접수 try {
*/
exports.receiveReport = (req, res) => {
const { id } = req.params; const { id } = req.params;
await workIssueModel.receiveReport(id, req.user.user_id);
workIssueModel.receiveReport(id, req.user.user_id, (err, result) => {
if (err) {
console.error('신고 접수 실패:', err);
return res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
}
res.json({ success: true, message: '신고가 접수되었습니다.' }); res.json({ success: true, message: '신고가 접수되었습니다.' });
}); } catch (err) {
logger.error('신고 접수 실패:', err);
res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
}
}; };
/** exports.assignReport = async (req, res) => {
* 담당자 배정 try {
*/
exports.assignReport = (req, res) => {
const { id } = req.params; const { id } = req.params;
const { assigned_department, assigned_user_id } = req.body; const { assigned_department, assigned_user_id } = req.body;
@@ -520,43 +403,34 @@ exports.assignReport = (req, res) => {
return res.status(400).json({ success: false, error: '담당자는 필수입니다.' }); return res.status(400).json({ success: false, error: '담당자는 필수입니다.' });
} }
workIssueModel.assignReport(id, { await workIssueModel.assignReport(id, {
assigned_department, assigned_department,
assigned_user_id, assigned_user_id,
assigned_by: req.user.user_id assigned_by: req.user.user_id
}, (err, result) => { });
if (err) {
console.error('담당자 배정 실패:', err);
return res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
}
res.json({ success: true, message: '담당자가 배정되었습니다.' }); res.json({ success: true, message: '담당자가 배정되었습니다.' });
}); } catch (err) {
}; logger.error('담당자 배정 실패:', err);
res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
/** }
* 처리 시작 };
*/
exports.startProcessing = (req, res) => { exports.startProcessing = async (req, res) => {
const { id } = req.params; try {
const { id } = req.params;
workIssueModel.startProcessing(id, req.user.user_id, (err, result) => { await workIssueModel.startProcessing(id, req.user.user_id);
if (err) { res.json({ success: true, message: '처리가 시작되었습니다.' });
console.error('처리 시작 실패:', err); } catch (err) {
return res.status(400).json({ success: false, error: err.message || '처리 시작 실패' }); logger.error('처리 시작 실패:', err);
res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
} }
res.json({ success: true, message: '처리가 시작되었습니다.' });
});
}; };
/**
* 처리 완료
*/
exports.completeReport = async (req, res) => { exports.completeReport = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { resolution_notes, resolution_photos = [] } = req.body; const { resolution_notes, resolution_photos = [] } = req.body;
// 완료 사진 저장
let resolution_photo_path1 = null; let resolution_photo_path1 = null;
let resolution_photo_path2 = null; let resolution_photo_path2 = null;
@@ -567,108 +441,83 @@ exports.completeReport = async (req, res) => {
resolution_photo_path2 = await imageUploadService.saveBase64Image(resolution_photos[1], 'resolution'); resolution_photo_path2 = await imageUploadService.saveBase64Image(resolution_photos[1], 'resolution');
} }
workIssueModel.completeReport(id, { await workIssueModel.completeReport(id, {
resolution_notes, resolution_notes,
resolution_photo_path1, resolution_photo_path1,
resolution_photo_path2, resolution_photo_path2,
resolved_by: req.user.user_id resolved_by: req.user.user_id
}, (err, result) => { });
if (err) {
console.error('처리 완료 실패:', err);
return res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
}
res.json({ success: true, message: '처리가 완료되었습니다.' }); res.json({ success: true, message: '처리가 완료되었습니다.' });
}); } catch (err) {
} catch (error) { logger.error('처리 완료 실패:', err);
console.error('처리 완료 에러:', error); res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
} }
}; };
/** exports.closeReport = async (req, res) => {
* 신고 종료 try {
*/
exports.closeReport = (req, res) => {
const { id } = req.params; const { id } = req.params;
await workIssueModel.closeReport(id, req.user.user_id);
workIssueModel.closeReport(id, req.user.user_id, (err, result) => {
if (err) {
console.error('신고 종료 실패:', err);
return res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
}
res.json({ success: true, message: '신고가 종료되었습니다.' }); res.json({ success: true, message: '신고가 종료되었습니다.' });
}); } catch (err) {
logger.error('신고 종료 실패:', err);
res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
}
}; };
/** exports.getStatusLogs = async (req, res) => {
* 상태 변경 이력 조회 try {
*/
exports.getStatusLogs = (req, res) => {
const { id } = req.params; const { id } = req.params;
const logs = await workIssueModel.getStatusLogs(id);
workIssueModel.getStatusLogs(id, (err, logs) => {
if (err) {
console.error('상태 이력 조회 실패:', err);
return res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
}
res.json({ success: true, data: logs }); res.json({ success: true, data: logs });
}); } catch (err) {
logger.error('상태 이력 조회 실패:', err);
res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
}
}; };
// ==================== 통계 ==================== // ==================== 통계 ====================
/** exports.getStatsSummary = async (req, res) => {
* 통계 요약 try {
*/
exports.getStatsSummary = (req, res) => {
const filters = { const filters = {
start_date: req.query.start_date, start_date: req.query.start_date,
end_date: req.query.end_date, end_date: req.query.end_date,
factory_category_id: req.query.factory_category_id factory_category_id: req.query.factory_category_id
}; };
const stats = await workIssueModel.getStatsSummary(filters);
workIssueModel.getStatsSummary(filters, (err, stats) => {
if (err) {
console.error('통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };
/** exports.getStatsByCategory = async (req, res) => {
* 카테고리별 통계 try {
*/
exports.getStatsByCategory = (req, res) => {
const filters = { const filters = {
start_date: req.query.start_date, start_date: req.query.start_date,
end_date: req.query.end_date end_date: req.query.end_date
}; };
const stats = await workIssueModel.getStatsByCategory(filters);
workIssueModel.getStatsByCategory(filters, (err, stats) => {
if (err) {
console.error('카테고리별 통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('카테고리별 통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };
/** exports.getStatsByWorkplace = async (req, res) => {
* 작업장별 통계 try {
*/
exports.getStatsByWorkplace = (req, res) => {
const filters = { const filters = {
start_date: req.query.start_date, start_date: req.query.start_date,
end_date: req.query.end_date, end_date: req.query.end_date,
factory_category_id: req.query.factory_category_id factory_category_id: req.query.factory_category_id
}; };
const stats = await workIssueModel.getStatsByWorkplace(filters);
workIssueModel.getStatsByWorkplace(filters, (err, stats) => {
if (err) {
console.error('작업장별 통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('작업장별 통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };

View File

@@ -8,15 +8,12 @@
*/ */
const workplaceModel = require('../models/workplaceModel'); const workplaceModel = require('../models/workplaceModel');
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); const { ValidationError, NotFoundError } = require('../utils/errors');
const { asyncHandler } = require('../middlewares/errorHandler'); const { asyncHandler } = require('../middlewares/errorHandler');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
// ==================== 카테고리(공장) 관련 ==================== // ==================== 카테고리(공장) 관련 ====================
/**
* 카테고리 생성
*/
exports.createCategory = asyncHandler(async (req, res) => { exports.createCategory = asyncHandler(async (req, res) => {
const categoryData = req.body; const categoryData = req.body;
@@ -26,12 +23,7 @@ exports.createCategory = asyncHandler(async (req, res) => {
logger.info('카테고리 생성 요청', { name: categoryData.category_name }); logger.info('카테고리 생성 요청', { name: categoryData.category_name });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createCategory(categoryData);
workplaceModel.createCategory(categoryData, (err, lastID) => {
if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('카테고리 생성 성공', { category_id: id }); logger.info('카테고리 생성 성공', { category_id: id });
@@ -42,16 +34,8 @@ exports.createCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 전체 카테고리 조회
*/
exports.getAllCategories = asyncHandler(async (req, res) => { exports.getAllCategories = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getAllCategories();
workplaceModel.getAllCategories((err, data) => {
if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -60,16 +44,8 @@ exports.getAllCategories = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 활성 카테고리만 조회
*/
exports.getActiveCategories = asyncHandler(async (req, res) => { exports.getActiveCategories = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getActiveCategories();
workplaceModel.getActiveCategories((err, data) => {
if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -78,18 +54,9 @@ exports.getActiveCategories = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 단일 카테고리 조회
*/
exports.getCategoryById = asyncHandler(async (req, res) => { exports.getCategoryById = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
const category = await workplaceModel.getCategoryById(categoryId);
const category = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!category) { if (!category) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
@@ -102,9 +69,6 @@ exports.getCategoryById = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리 수정
*/
exports.updateCategory = asyncHandler(async (req, res) => { exports.updateCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
const categoryData = req.body; const categoryData = req.body;
@@ -115,19 +79,11 @@ exports.updateCategory = asyncHandler(async (req, res) => {
logger.info('카테고리 수정 요청', { category_id: categoryId }); logger.info('카테고리 수정 요청', { category_id: categoryId });
// 기존 카테고리 정보 가져오기 const existingCategory = await workplaceModel.getCategoryById(categoryId);
const existingCategory = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!existingCategory) { if (!existingCategory) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
} }
// layout_image가 요청에 없거나 null이면 기존 값 보존
const updateData = { const updateData = {
...categoryData, ...categoryData,
layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null) layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null)
@@ -135,12 +91,7 @@ exports.updateCategory = asyncHandler(async (req, res) => {
: existingCategory.layout_image : existingCategory.layout_image
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateCategory(categoryId, updateData);
workplaceModel.updateCategory(categoryId, updateData, (err, result) => {
if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('카테고리 수정 성공', { category_id: categoryId }); logger.info('카테고리 수정 성공', { category_id: categoryId });
@@ -150,20 +101,12 @@ exports.updateCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리 삭제
*/
exports.deleteCategory = asyncHandler(async (req, res) => { exports.deleteCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
logger.info('카테고리 삭제 요청', { category_id: categoryId }); logger.info('카테고리 삭제 요청', { category_id: categoryId });
await new Promise((resolve, reject) => { await workplaceModel.deleteCategory(categoryId);
workplaceModel.deleteCategory(categoryId, (err, result) => {
if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('카테고리 삭제 성공', { category_id: categoryId }); logger.info('카테고리 삭제 성공', { category_id: categoryId });
@@ -175,9 +118,6 @@ exports.deleteCategory = asyncHandler(async (req, res) => {
// ==================== 작업장 관련 ==================== // ==================== 작업장 관련 ====================
/**
* 작업장 생성
*/
exports.createWorkplace = asyncHandler(async (req, res) => { exports.createWorkplace = asyncHandler(async (req, res) => {
const workplaceData = req.body; const workplaceData = req.body;
@@ -187,12 +127,7 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
logger.info('작업장 생성 요청', { name: workplaceData.workplace_name }); logger.info('작업장 생성 요청', { name: workplaceData.workplace_name });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createWorkplace(workplaceData);
workplaceModel.createWorkplace(workplaceData, (err, lastID) => {
if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('작업장 생성 성공', { workplace_id: id }); logger.info('작업장 생성 성공', { workplace_id: id });
@@ -203,35 +138,12 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 전체 작업장 조회
*/
exports.getAllWorkplaces = asyncHandler(async (req, res) => { exports.getAllWorkplaces = asyncHandler(async (req, res) => {
const categoryId = req.query.category_id; const categoryId = req.query.category_id;
// 카테고리별 필터링 const rows = categoryId
if (categoryId) { ? await workplaceModel.getWorkplacesByCategory(categoryId)
const rows = await new Promise((resolve, reject) => { : await workplaceModel.getAllWorkplaces();
workplaceModel.getWorkplacesByCategory(categoryId, (err, data) => {
if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
return res.json({
success: true,
data: rows,
message: '작업장 목록 조회 성공'
});
}
// 전체 조회
const rows = await new Promise((resolve, reject) => {
workplaceModel.getAllWorkplaces((err, data) => {
if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -240,16 +152,8 @@ exports.getAllWorkplaces = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 활성 작업장만 조회
*/
exports.getActiveWorkplaces = asyncHandler(async (req, res) => { exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getActiveWorkplaces();
workplaceModel.getActiveWorkplaces((err, data) => {
if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -258,18 +162,9 @@ exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 단일 작업장 조회
*/
exports.getWorkplaceById = asyncHandler(async (req, res) => { exports.getWorkplaceById = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
const workplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!workplace) { if (!workplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
@@ -282,9 +177,6 @@ exports.getWorkplaceById = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 수정
*/
exports.updateWorkplace = asyncHandler(async (req, res) => { exports.updateWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
const workplaceData = req.body; const workplaceData = req.body;
@@ -295,19 +187,11 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
logger.info('작업장 수정 요청', { workplace_id: workplaceId }); logger.info('작업장 수정 요청', { workplace_id: workplaceId });
// 기존 작업장 정보 가져오기 const existingWorkplace = await workplaceModel.getWorkplaceById(workplaceId);
const existingWorkplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!existingWorkplace) { if (!existingWorkplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
} }
// layout_image가 요청에 없거나 null이면 기존 값 보존
const updateData = { const updateData = {
...workplaceData, ...workplaceData,
layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null) layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null)
@@ -315,12 +199,7 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
: existingWorkplace.layout_image : existingWorkplace.layout_image
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateWorkplace(workplaceId, updateData);
workplaceModel.updateWorkplace(workplaceId, updateData, (err, result) => {
if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 수정 성공', { workplace_id: workplaceId }); logger.info('작업장 수정 성공', { workplace_id: workplaceId });
@@ -330,20 +209,12 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 삭제
*/
exports.deleteWorkplace = asyncHandler(async (req, res) => { exports.deleteWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
logger.info('작업장 삭제 요청', { workplace_id: workplaceId }); logger.info('작업장 삭제 요청', { workplace_id: workplaceId });
await new Promise((resolve, reject) => { await workplaceModel.deleteWorkplace(workplaceId);
workplaceModel.deleteWorkplace(workplaceId, (err, result) => {
if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 삭제 성공', { workplace_id: workplaceId }); logger.info('작업장 삭제 성공', { workplace_id: workplaceId });
@@ -355,9 +226,6 @@ exports.deleteWorkplace = asyncHandler(async (req, res) => {
// ==================== 작업장 지도 영역 관련 ==================== // ==================== 작업장 지도 영역 관련 ====================
/**
* 카테고리 레이아웃 이미지 업로드
*/
exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => { exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
@@ -369,19 +237,11 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath }); logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath });
// 현재 카테고리 정보 가져오기 const category = await workplaceModel.getCategoryById(categoryId);
const category = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!category) { if (!category) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
} }
// 카테고리 정보 업데이트 (이미지 경로만 변경)
const updatedData = { const updatedData = {
category_name: category.category_name, category_name: category.category_name,
description: category.description, description: category.description,
@@ -390,12 +250,7 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
layout_image: imagePath layout_image: imagePath
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateCategory(categoryId, updatedData);
workplaceModel.updateCategory(categoryId, updatedData, (err, result) => {
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId }); logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId });
@@ -406,9 +261,6 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 레이아웃 이미지 업로드
*/
exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => { exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
@@ -420,19 +272,11 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath }); logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath });
// 현재 작업장 정보 가져오기 const workplace = await workplaceModel.getWorkplaceById(workplaceId);
const workplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!workplace) { if (!workplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
} }
// 작업장 정보 업데이트 (이미지 경로만 변경)
const updatedData = { const updatedData = {
workplace_name: workplace.workplace_name, workplace_name: workplace.workplace_name,
category_id: workplace.category_id, category_id: workplace.category_id,
@@ -443,12 +287,7 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
layout_image: imagePath layout_image: imagePath
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateWorkplace(workplaceId, updatedData);
workplaceModel.updateWorkplace(workplaceId, updatedData, (err, result) => {
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId }); logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId });
@@ -459,9 +298,6 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 생성
*/
exports.createMapRegion = asyncHandler(async (req, res) => { exports.createMapRegion = asyncHandler(async (req, res) => {
const regionData = req.body; const regionData = req.body;
@@ -471,12 +307,7 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id }); logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createMapRegion(regionData);
workplaceModel.createMapRegion(regionData, (err, lastID) => {
if (err) reject(new DatabaseError('지도 영역 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('지도 영역 생성 성공', { region_id: id }); logger.info('지도 영역 생성 성공', { region_id: id });
@@ -487,18 +318,9 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리별 지도 영역 조회 (작업장 정보 포함)
*/
exports.getMapRegionsByCategory = asyncHandler(async (req, res) => { exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.categoryId; const categoryId = req.params.categoryId;
const rows = await workplaceModel.getMapRegionsByCategory(categoryId);
const rows = await new Promise((resolve, reject) => {
workplaceModel.getMapRegionsByCategory(categoryId, (err, data) => {
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -507,18 +329,9 @@ exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장별 지도 영역 조회
*/
exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => { exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.workplaceId; const workplaceId = req.params.workplaceId;
const region = await workplaceModel.getMapRegionByWorkplace(workplaceId);
const region = await new Promise((resolve, reject) => {
workplaceModel.getMapRegionByWorkplace(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -527,21 +340,13 @@ exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 수정
*/
exports.updateMapRegion = asyncHandler(async (req, res) => { exports.updateMapRegion = asyncHandler(async (req, res) => {
const regionId = req.params.id; const regionId = req.params.id;
const regionData = req.body; const regionData = req.body;
logger.info('지도 영역 수정 요청', { region_id: regionId }); logger.info('지도 영역 수정 요청', { region_id: regionId });
await new Promise((resolve, reject) => { await workplaceModel.updateMapRegion(regionId, regionData);
workplaceModel.updateMapRegion(regionId, regionData, (err, result) => {
if (err) reject(new DatabaseError('지도 영역 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('지도 영역 수정 성공', { region_id: regionId }); logger.info('지도 영역 수정 성공', { region_id: regionId });
@@ -551,20 +356,12 @@ exports.updateMapRegion = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 삭제
*/
exports.deleteMapRegion = asyncHandler(async (req, res) => { exports.deleteMapRegion = asyncHandler(async (req, res) => {
const regionId = req.params.id; const regionId = req.params.id;
logger.info('지도 영역 삭제 요청', { region_id: regionId }); logger.info('지도 영역 삭제 요청', { region_id: regionId });
await new Promise((resolve, reject) => { await workplaceModel.deleteMapRegion(regionId);
workplaceModel.deleteMapRegion(regionId, (err, result) => {
if (err) reject(new DatabaseError('지도 영역 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('지도 영역 삭제 성공', { region_id: regionId }); logger.info('지도 영역 삭제 성공', { region_id: regionId });

View File

@@ -2,8 +2,6 @@ const { getDb } = require('../dbPool');
/** /**
* 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다. * 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다.
* @param {Array<object>} reports - 생성할 보고서 데이터 배열
* @returns {Promise<Array<number>>} - 삽입된 ID 배열
*/ */
const createMany = async (reports) => { const createMany = async (reports) => {
const db = await getDb(); const db = await getDb();
@@ -36,10 +34,9 @@ const createMany = async (reports) => {
}; };
/** /**
* 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반) * 2. 특정 날짜의 전체 이슈 목록 조회
*/ */
const getAllByDate = async (date) => { const getAllByDate = async (date) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT `SELECT
@@ -54,30 +51,21 @@ const getAllByDate = async (date) => {
[date] [date]
); );
return rows; return rows;
} catch (err) {
console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err);
throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.');
}
}; };
/** /**
* 3. 단일 조회 (선택사항: 컨트롤러에서 사용 중) * 3. 단일 조회
*/ */
const getById = async (id, callback) => { const getById = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query(`SELECT id, date, worker_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]); const [rows] = await db.query(`SELECT id, date, worker_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]);
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** /**
* 4. 수정 * 4. 수정
*/ */
const update = async (id, data, callback) => { const update = async (id, data) => {
try {
const db = await getDb(); const db = await getDb();
const fields = []; const fields = [];
@@ -88,67 +76,29 @@ const update = async (id, data, callback) => {
values.push(data[key]); values.push(data[key]);
} }
values.push(id); // 마지막에 id values.push(id);
const [result] = await db.query( const [result] = await db.query(
`UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`, `UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`,
values values
); );
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) {
callback(err);
}
}; };
/** /**
* 5. 삭제 (Promise 기반) * 5. 삭제
*/ */
const remove = async (id) => { const remove = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]); const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
return result.affectedRows; return result.affectedRows;
} catch (err) {
console.error(`[Model] 이슈(id: ${id}) 삭제 오류:`, err);
throw new Error('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.');
}
}; };
// V1 함수들은 점진적으로 제거 예정
const create = async (report, callback) => {
try {
const db = await getDb();
const {
date,
worker_id,
project_id,
start_time,
end_time,
issue_type_id,
description = null // 선택값 처리
} = report;
const [result] = await db.query(
`INSERT INTO DailyIssueReports
(date, worker_id, project_id, start_time, end_time, issue_type_id, description)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[date, worker_id, project_id, start_time, end_time, issue_type_id, description]
);
callback(null, result.insertId);
} catch (err) {
callback(err);
}
};
module.exports = { module.exports = {
createMany, // 신규 createMany,
getAllByDate, getAllByDate,
remove,
// 레거시 호환성을 위해 V1 함수들 임시 유지
create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)),
getById, getById,
update, update,
remove
}; };

View File

@@ -2,34 +2,22 @@
const { getDb } = require('../dbPool'); const { getDb } = require('../dbPool');
/** /**
* 📋 마스터 데이터 조회 함수들 * 마스터 데이터 조회 함수들
*/ */
const getAllWorkTypes = async (callback) => { const getAllWorkTypes = async () => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query('SELECT id, name, description, category, created_at, updated_at FROM work_types ORDER BY name ASC'); const [rows] = await db.query('SELECT id, name, description, category, created_at, updated_at FROM work_types ORDER BY name ASC');
callback(null, rows); return rows;
} catch (err) {
console.error('작업 유형 조회 오류:', err);
callback(err);
}
}; };
const getAllWorkStatusTypes = async (callback) => { const getAllWorkStatusTypes = async () => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query('SELECT id, name, description, is_error, created_at FROM work_status_types ORDER BY id ASC'); const [rows] = await db.query('SELECT id, name, description, is_error, created_at FROM work_status_types ORDER BY id ASC');
callback(null, rows); return rows;
} catch (err) {
console.error('업무 상태 유형 조회 오류:', err);
callback(err);
}
}; };
const getAllErrorTypes = async (callback) => { const getAllErrorTypes = async () => {
try {
const db = await getDb(); const db = await getDb();
// issue_report_items에서 부적합(nonconformity) 타입의 항목만 조회
const [rows] = await db.query(` const [rows] = await db.query(`
SELECT SELECT
iri.item_id as id, iri.item_id as id,
@@ -44,17 +32,13 @@ const getAllErrorTypes = async (callback) => {
WHERE irc.category_type = 'nonconformity' AND iri.is_active = TRUE WHERE irc.category_type = 'nonconformity' AND iri.is_active = TRUE
ORDER BY irc.display_order, iri.display_order, iri.item_name ASC ORDER BY irc.display_order, iri.display_order, iri.item_name ASC
`); `);
callback(null, rows); return rows;
} catch (err) {
console.error('에러 유형 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 🔄 누적 추가 전용 함수 (createDailyReport 대체) - 절대 삭제 안함! * 누적 추가 전용 함수 (createDailyReport 대체) - 절대 삭제 안함!
*/ */
const createDailyReport = async (reportData, callback) => { const createDailyReport = async (reportData) => {
const { report_date, worker_id, work_entries, created_by, created_by_name, total_hours } = reportData; const { report_date, worker_id, work_entries, created_by, created_by_name, total_hours } = reportData;
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -62,9 +46,8 @@ const createDailyReport = async (reportData, callback) => {
try { try {
await conn.beginTransaction(); await conn.beginTransaction();
console.log(`📝 ${created_by_name}${report_date} ${worker_id}번 작업자에게 데이터 추가 중...`); console.log(`${created_by_name}${report_date} ${worker_id}번 작업자에게 데이터 추가 중...`);
// ✅ 수정된 쿼리 (테이블 alias 추가):
const [existingReports] = await conn.query( const [existingReports] = await conn.query(
`SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours `SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours
FROM daily_work_reports dwr FROM daily_work_reports dwr
@@ -74,10 +57,9 @@ const createDailyReport = async (reportData, callback) => {
[report_date, worker_id] [report_date, worker_id]
); );
console.log('기존 데이터 (삭제하지 않음):', existingReports); console.log('기존 데이터 (삭제하지 않음):', existingReports);
// 2. ✅ 삭제 없이 새로운 데이터만 추가! // 삭제 없이 새로운 데이터만 추가!
const insertedIds = []; const insertedIds = [];
for (const entry of work_entries) { for (const entry of work_entries) {
const { project_id, work_type_id, work_status_id, error_type_id, work_hours } = entry; const { project_id, work_type_id, work_status_id, error_type_id, work_hours } = entry;
@@ -92,7 +74,6 @@ const createDailyReport = async (reportData, callback) => {
insertedIds.push(insertResult.insertId); insertedIds.push(insertResult.insertId);
} }
// ✅ 수정된 쿼리:
const [finalReports] = await conn.query( const [finalReports] = await conn.query(
`SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours `SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours
FROM daily_work_reports dwr FROM daily_work_reports dwr
@@ -109,9 +90,9 @@ const createDailyReport = async (reportData, callback) => {
finalReports.forEach(report => { finalReports.forEach(report => {
console.log(` - ${report.created_by_name}: ${report.total_hours}시간 (${report.count}개 항목)`); console.log(` - ${report.created_by_name}: ${report.total_hours}시간 (${report.count}개 항목)`);
}); });
console.log(` 📊 총합: ${grandTotal}시간`); console.log(` 총합: ${grandTotal}시간`);
// 4. 감사 로그 추가 // 감사 로그 추가
try { try {
await conn.query( await conn.query(
`INSERT INTO work_report_audit_log `INSERT INTO work_report_audit_log
@@ -139,16 +120,15 @@ const createDailyReport = async (reportData, callback) => {
await conn.commit(); await conn.commit();
// 5. 근태 기록 동기화 (추가) // 근태 기록 동기화
try { try {
const AttendanceModel = require('./attendanceModel'); const AttendanceModel = require('./attendanceModel');
await AttendanceModel.syncWithWorkReports(worker_id, report_date); await AttendanceModel.syncWithWorkReports(worker_id, report_date);
} catch (syncErr) { } catch (syncErr) {
console.error('근태 기록 동기화 실패:', syncErr); console.error('근태 기록 동기화 실패:', syncErr);
// 메인 트랜잭션은 성공했으므로 동기화 실패로 롤백하지 않음 (비동기 처리 또는 무시)
} }
callback(null, { return {
success: true, success: true,
inserted_count: insertedIds.length, inserted_count: insertedIds.length,
deleted_count: 0, // 항상 0 (삭제 안함) deleted_count: 0, // 항상 0 (삭제 안함)
@@ -160,22 +140,20 @@ const createDailyReport = async (reportData, callback) => {
total_contributors: finalReports.length, total_contributors: finalReports.length,
contributors: finalReports contributors: finalReports
} }
}); };
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('작업보고서 누적 추가 오류:', err); throw err;
callback(err);
} finally { } finally {
conn.release(); conn.release();
} }
}; };
/** /**
* 📊 특정 날짜 + 작업자 + 작성자의 누적 현황 조회 * 특정 날짜 + 작업자 + 작성자의 누적 현황 조회
*/ */
const getMyAccumulatedHours = async (date, worker_id, created_by, callback) => { const getMyAccumulatedHours = async (date, worker_id, created_by) => {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
@@ -192,18 +170,13 @@ const getMyAccumulatedHours = async (date, worker_id, created_by, callback) => {
`; `;
const [rows] = await db.query(sql, [date, worker_id, created_by]); const [rows] = await db.query(sql, [date, worker_id, created_by]);
callback(null, rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null }); return rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null };
} catch (err) {
console.error('개인 누적 현황 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 📊 누적 현황 조회 - 날짜+작업자별 (모든 기여자) * 누적 현황 조회 - 날짜+작업자별 (모든 기여자)
*/ */
const getAccumulatedReportsByDate = async (date, worker_id, callback) => { const getAccumulatedReportsByDate = async (date, worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
@@ -212,18 +185,13 @@ const getAccumulatedReportsByDate = async (date, worker_id, callback) => {
`; `;
const [rows] = await db.query(sql, [date, worker_id]); const [rows] = await db.query(sql, [date, worker_id]);
callback(null, rows); return rows;
} catch (err) {
console.error('누적 현황 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 📊 기여자별 요약 조회 * 기여자별 요약 조회
*/ */
const getContributorsByDate = async (date, worker_id, callback) => { const getContributorsByDate = async (date, worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
@@ -247,17 +215,13 @@ const getContributorsByDate = async (date, worker_id, callback) => {
`; `;
const [rows] = await db.query(sql, [date, worker_id]); const [rows] = await db.query(sql, [date, worker_id]);
callback(null, rows); return rows;
} catch (err) {
console.error('기여자별 요약 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 🗑️ 특정 작업 항목만 삭제 (개별 삭제) * 특정 작업 항목만 삭제 (개별 삭제)
*/ */
const removeSpecificEntry = async (entry_id, deleted_by, callback) => { const removeSpecificEntry = async (entry_id, deleted_by) => {
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -276,16 +240,14 @@ const removeSpecificEntry = async (entry_id, deleted_by, callback) => {
); );
if (entryInfo.length === 0) { if (entryInfo.length === 0) {
await conn.rollback(); throw new Error('삭제할 항목을 찾을 수 없습니다.');
return callback(new Error('삭제할 항목을 찾을 수 없습니다.'));
} }
const entry = entryInfo[0]; const entry = entryInfo[0];
// 권한 확인: 본인이 작성한 것만 삭제 가능 // 권한 확인: 본인이 작성한 것만 삭제 가능
if (entry.created_by !== deleted_by) { if (entry.created_by !== deleted_by) {
await conn.rollback(); throw new Error('본인이 작성한 항목만 삭제할 수 있습니다.');
return callback(new Error('본인이 작성한 항목만 삭제할 수 있습니다.'));
} }
// 개별 항목 삭제 // 개별 항목 삭제
@@ -295,19 +257,18 @@ const removeSpecificEntry = async (entry_id, deleted_by, callback) => {
console.log(`[삭제 로그] 작업자: ${entry.worker_name}, 프로젝트: ${entry.project_name}, 작업시간: ${entry.work_hours}시간, 삭제자: ${deleted_by}`); console.log(`[삭제 로그] 작업자: ${entry.worker_name}, 프로젝트: ${entry.project_name}, 작업시간: ${entry.work_hours}시간, 삭제자: ${deleted_by}`);
await conn.commit(); await conn.commit();
callback(null, { return {
success: true, success: true,
deleted_entry: { deleted_entry: {
worker_name: entry.worker_name, worker_name: entry.worker_name,
project_name: entry.project_name, project_name: entry.project_name,
work_hours: entry.work_hours work_hours: entry.work_hours
} }
}); };
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('개별 항목 삭제 오류:', err); throw err;
callback(err);
} finally { } finally {
conn.release(); conn.release();
} }
@@ -350,115 +311,84 @@ const getSelectQuery = () => `
/** /**
* 7. ID로 작업보고서 조회 * 7. ID로 작업보고서 조회
*/ */
const getById = async (id, callback) => { const getById = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + 'WHERE dwr.id = ?'; const sql = getSelectQuery() + 'WHERE dwr.id = ?';
const [rows] = await db.query(sql, [id]); const [rows] = await db.query(sql, [id]);
callback(null, rows[0] || null); return rows[0] || null;
} catch (err) {
console.error('ID로 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 8. 일일 작업보고서 조회 (날짜별) * 8. 일일 작업보고서 조회 (날짜별)
*/ */
const getByDate = async (date, callback) => { const getByDate = async (date) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
WHERE dwr.report_date = ? WHERE dwr.report_date = ?
ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC
`; `;
const [rows] = await db.query(sql, [date]); const [rows] = await db.query(sql, [date]);
callback(null, rows); return rows;
} catch (err) {
console.error('날짜별 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 9. 일일 작업보고서 조회 (날짜 + 작성자별) * 9. 일일 작업보고서 조회 (날짜 + 작성자별)
*/ */
const getByDateAndCreator = async (date, created_by, callback) => { const getByDateAndCreator = async (date, created_by) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
WHERE dwr.report_date = ? AND dwr.created_by = ? WHERE dwr.report_date = ? AND dwr.created_by = ?
ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC
`; `;
const [rows] = await db.query(sql, [date, created_by]); const [rows] = await db.query(sql, [date, created_by]);
callback(null, rows); return rows;
} catch (err) {
console.error('날짜+작성자별 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 10. 일일 작업보고서 조회 (작업자별) * 10. 일일 작업보고서 조회 (작업자별)
*/ */
const getByWorker = async (worker_id, callback) => { const getByWorker = async (worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
WHERE dwr.worker_id = ? WHERE dwr.worker_id = ?
ORDER BY dwr.report_date DESC, dwr.id ASC ORDER BY dwr.report_date DESC, dwr.id ASC
`; `;
const [rows] = await db.query(sql, [worker_id]); const [rows] = await db.query(sql, [worker_id]);
callback(null, rows); return rows;
} catch (err) {
console.error('작업자별 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 11. 일일 작업보고서 조회 (날짜 + 작업자) * 11. 일일 작업보고서 조회 (날짜 + 작업자)
*/ */
const getByDateAndWorker = async (date, worker_id, callback) => { const getByDateAndWorker = async (date, worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
WHERE dwr.report_date = ? AND dwr.worker_id = ? WHERE dwr.report_date = ? AND dwr.worker_id = ?
ORDER BY dwr.id ASC ORDER BY dwr.id ASC
`; `;
const [rows] = await db.query(sql, [date, worker_id]); const [rows] = await db.query(sql, [date, worker_id]);
callback(null, rows); return rows;
} catch (err) {
console.error('날짜+작업자별 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 12. 기간별 조회 * 12. 기간별 조회
*/ */
const getByRange = async (start_date, end_date, callback) => { const getByRange = async (start_date, end_date) => {
try {
const db = await getDb(); const db = await getDb();
const sql = getSelectQuery() + ` const sql = getSelectQuery() + `
WHERE dwr.report_date BETWEEN ? AND ? WHERE dwr.report_date BETWEEN ? AND ?
ORDER BY dwr.report_date DESC, w.worker_name ASC, dwr.id ASC ORDER BY dwr.report_date DESC, w.worker_name ASC, dwr.id ASC
`; `;
const [rows] = await db.query(sql, [start_date, end_date]); const [rows] = await db.query(sql, [start_date, end_date]);
callback(null, rows); return rows;
} catch (err) {
console.error('기간별 작업보고서 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 13. 상세 검색 (페이지네이션 포함) * 13. 상세 검색 (페이지네이션 포함)
*/ */
const searchWithDetails = async (params, callback) => { const searchWithDetails = async (params) => {
const { start_date, end_date, worker_id, project_id, work_status_id, created_by, page, limit } = params; const { start_date, end_date, worker_id, project_id, work_status_id, created_by, page, limit } = params;
try {
const db = await getDb(); const db = await getDb();
// 조건 구성 // 조건 구성
@@ -507,18 +437,13 @@ const searchWithDetails = async (params, callback) => {
const dataParams = [...queryParams, limit, offset]; const dataParams = [...queryParams, limit, offset];
const [rows] = await db.query(dataQuery, dataParams); const [rows] = await db.query(dataQuery, dataParams);
callback(null, { reports: rows, total }); return { reports: rows, total };
} catch (err) {
console.error('상세 검색 오류:', err);
callback(err);
}
}; };
/** /**
* 14. 일일 근무 요약 조회 (날짜별) * 14. 일일 근무 요약 조회 (날짜별)
*/ */
const getSummaryByDate = async (date, callback) => { const getSummaryByDate = async (date) => {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
@@ -536,18 +461,13 @@ const getSummaryByDate = async (date, callback) => {
ORDER BY w.worker_name ASC ORDER BY w.worker_name ASC
`; `;
const [rows] = await db.query(sql, [date]); const [rows] = await db.query(sql, [date]);
callback(null, rows); return rows;
} catch (err) {
console.error('일일 근무 요약 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 15. 일일 근무 요약 조회 (작업자별) * 15. 일일 근무 요약 조회 (작업자별)
*/ */
const getSummaryByWorker = async (worker_id, callback) => { const getSummaryByWorker = async (worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
@@ -565,18 +485,13 @@ const getSummaryByWorker = async (worker_id, callback) => {
ORDER BY dwr.report_date DESC ORDER BY dwr.report_date DESC
`; `;
const [rows] = await db.query(sql, [worker_id]); const [rows] = await db.query(sql, [worker_id]);
callback(null, rows); return rows;
} catch (err) {
console.error('작업자별 근무 요약 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 16. 월간 요약 * 16. 월간 요약
*/ */
const getMonthlySummary = async (year, month, callback) => { const getMonthlySummary = async (year, month) => {
try {
const db = await getDb(); const db = await getDb();
const start = `${year.padStart(4, '0')}-${month.padStart(2, '0')}-01`; const start = `${year.padStart(4, '0')}-${month.padStart(2, '0')}-01`;
const end = `${year.padStart(4, '0')}-${month.padStart(2, '0')}-31`; const end = `${year.padStart(4, '0')}-${month.padStart(2, '0')}-31`;
@@ -601,18 +516,13 @@ const getMonthlySummary = async (year, month, callback) => {
ORDER BY dwr.report_date DESC, w.worker_name ASC ORDER BY dwr.report_date DESC, w.worker_name ASC
`; `;
const [rows] = await db.query(sql, [start, end]); const [rows] = await db.query(sql, [start, end]);
callback(null, rows); return rows;
} catch (err) {
console.error('월간 요약 조회 오류:', err);
callback(err);
}
}; };
/** /**
* 17. 작업보고서 수정 * 17. 작업보고서 수정
*/ */
const updateById = async (id, updateData, callback) => { const updateById = async (id, updateData) => {
try {
const db = await getDb(); const db = await getDb();
const setFields = []; const setFields = [];
@@ -655,8 +565,6 @@ const updateById = async (id, updateData, callback) => {
const sql = `UPDATE daily_work_reports SET ${setFields.join(', ')} WHERE id = ?`; const sql = `UPDATE daily_work_reports SET ${setFields.join(', ')} WHERE id = ?`;
const [result] = await db.query(sql, values); const [result] = await db.query(sql, values);
// [Sync] 근태 기록 동기화 // [Sync] 근태 기록 동기화
try { try {
const [targetReport] = await db.query('SELECT worker_id, report_date FROM daily_work_reports WHERE id = ?', [id]); const [targetReport] = await db.query('SELECT worker_id, report_date FROM daily_work_reports WHERE id = ?', [id]);
@@ -669,17 +577,13 @@ const updateById = async (id, updateData, callback) => {
console.error('근태 기록 동기화 실패 (Update):', syncErr); console.error('근태 기록 동기화 실패 (Update):', syncErr);
} }
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) {
console.error('작업보고서 수정 오류:', err);
callback(err);
}
}; };
/** /**
* 18. 특정 작업보고서 삭제 * 18. 특정 작업보고서 삭제
*/ */
const removeById = async (id, deletedBy, callback) => { const removeById = async (id, deletedBy) => {
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -708,7 +612,6 @@ const removeById = async (id, deletedBy, callback) => {
await conn.commit(); await conn.commit();
// [Sync] 근태 기록 동기화 // [Sync] 근태 기록 동기화
if (reportInfo.length > 0) { if (reportInfo.length > 0) {
try { try {
@@ -720,11 +623,10 @@ const removeById = async (id, deletedBy, callback) => {
} }
} }
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('작업보고서 삭제 오류:', err); throw err;
callback(err);
} finally { } finally {
conn.release(); conn.release();
} }
@@ -733,7 +635,7 @@ const removeById = async (id, deletedBy, callback) => {
/** /**
* 19. 작업자의 특정 날짜 전체 삭제 * 19. 작업자의 특정 날짜 전체 삭제
*/ */
const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => { const removeByDateAndWorker = async (date, worker_id, deletedBy) => {
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -768,7 +670,6 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
await conn.commit(); await conn.commit();
// [Sync] 근태 기록 동기화 // [Sync] 근태 기록 동기화
try { try {
const AttendanceModel = require('./attendanceModel'); const AttendanceModel = require('./attendanceModel');
@@ -777,21 +678,19 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
console.error('근태 기록 동기화 실패 (Batch Delete):', syncErr); console.error('근태 기록 동기화 실패 (Batch Delete):', syncErr);
} }
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('작업보고서 전체 삭제 오류:', err); throw err;
callback(err);
} finally { } finally {
conn.release(); conn.release();
} }
}; };
/** /**
* 20. 통계 조회 (Promise 기반) * 20. 통계 조회
*/ */
const getStatistics = async (start_date, end_date) => { const getStatistics = async (start_date, end_date) => {
try {
const db = await getDb(); const db = await getDb();
const overallSql = ` const overallSql = `
@@ -821,14 +720,10 @@ const getStatistics = async (start_date, end_date) => {
overall: overallRows[0], overall: overallRows[0],
daily_breakdown: dailyStats daily_breakdown: dailyStats
}; };
} catch (err) {
console.error('통계 조회 오류:', err);
throw new Error('데이터베이스에서 통계 정보를 조회하는 중 오류가 발생했습니다.');
}
}; };
/** /**
* [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다. (Promise 기반) * [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다.
* @param {object} modelData - 서비스 레이어에서 전달된 데이터 * @param {object} modelData - 서비스 레이어에서 전달된 데이터
* @returns {Promise<object>} 삽입된 항목의 ID 배열과 개수 * @returns {Promise<object>} 삽입된 항목의 ID 배열과 개수
*/ */
@@ -862,8 +757,6 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => {
await conn.commit(); await conn.commit();
// [Sync] 근태 기록 동기화 // [Sync] 근태 기록 동기화
try { try {
const AttendanceModel = require('./attendanceModel'); const AttendanceModel = require('./attendanceModel');
@@ -879,9 +772,7 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => {
}; };
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('[Model] 작업 보고서 생성 중 오류 발생:', err);
// 여기서 발생한 에러는 서비스 레이어로 전파됩니다.
throw new Error('데이터베이스에 작업 보고서를 생성하는 중 오류가 발생했습니다.'); throw new Error('데이터베이스에 작업 보고서를 생성하는 중 오류가 발생했습니다.');
} finally { } finally {
conn.release(); conn.release();
@@ -925,7 +816,7 @@ const getSelectQueryV2 = () => `
`; `;
/** /**
* [V2] 옵션 기반으로 작업 보고서를 조회합니다. (Promise 기반) * [V2] 옵션 기반으로 작업 보고서를 조회합니다.
* @param {object} options - 조회 조건 (date, worker_id, created_by_user_id 등) * @param {object} options - 조회 조건 (date, worker_id, created_by_user_id 등)
* @returns {Promise<Array>} 조회된 작업 보고서 배열 * @returns {Promise<Array>} 조회된 작업 보고서 배열
*/ */
@@ -951,7 +842,6 @@ const getReportsWithOptions = async (options) => {
whereConditions.push('dwr.created_by = ?'); whereConditions.push('dwr.created_by = ?');
queryParams.push(options.created_by_user_id); queryParams.push(options.created_by_user_id);
} }
// 필요에 따라 다른 조건 추가 가능 (project_id 등)
if (whereConditions.length === 0) { if (whereConditions.length === 0) {
throw new Error('조회 조건이 하나 이상 필요합니다.'); throw new Error('조회 조건이 하나 이상 필요합니다.');
@@ -960,17 +850,12 @@ const getReportsWithOptions = async (options) => {
const whereClause = whereConditions.join(' AND '); const whereClause = whereConditions.join(' AND ');
const sql = `${getSelectQueryV2()} WHERE ${whereClause} ORDER BY w.worker_name ASC, p.project_name ASC`; const sql = `${getSelectQueryV2()} WHERE ${whereClause} ORDER BY w.worker_name ASC, p.project_name ASC`;
try {
const [rows] = await db.query(sql, queryParams); const [rows] = await db.query(sql, queryParams);
return rows; return rows;
} catch (err) {
console.error('[Model] 작업 보고서 조회 오류:', err);
throw new Error('데이터베이스에서 작업 보고서를 조회하는 중 오류가 발생했습니다.');
}
}; };
/** /**
* [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다. (Promise 기반) * [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다.
* @param {string} reportId - 수정할 보고서의 ID * @param {string} reportId - 수정할 보고서의 ID
* @param {object} updateData - 수정할 필드와 값 * @param {object} updateData - 수정할 필드와 값
* @returns {Promise<number>} 영향을 받은 행의 수 * @returns {Promise<number>} 영향을 받은 행의 수
@@ -1011,7 +896,6 @@ const updateReportById = async (reportId, updateData) => {
const sql = `UPDATE daily_work_reports SET ${setClauses.join(', ')} WHERE id = ?`; const sql = `UPDATE daily_work_reports SET ${setClauses.join(', ')} WHERE id = ?`;
try {
const [result] = await db.query(sql, queryParams); const [result] = await db.query(sql, queryParams);
// [Sync] 근태 기록 동기화 // [Sync] 근태 기록 동기화
@@ -1025,14 +909,10 @@ const updateReportById = async (reportId, updateData) => {
} }
return result.affectedRows; return result.affectedRows;
} catch (err) {
console.error(`[Model] 작업 보고서 수정 오류 (id: ${reportId}):`, err);
throw new Error('데이터베이스에서 작업 보고서를 수정하는 중 오류가 발생했습니다.');
}
}; };
/** /**
* [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다. (Promise 기반) * [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다.
* @param {string} reportId - 삭제할 보고서 ID * @param {string} reportId - 삭제할 보고서 ID
* @param {number} deletedByUserId - 삭제를 수행하는 사용자 ID * @param {number} deletedByUserId - 삭제를 수행하는 사용자 ID
* @returns {Promise<number>} 영향을 받은 행의 수 * @returns {Promise<number>} 영향을 받은 행의 수
@@ -1071,9 +951,8 @@ const removeReportById = async (reportId, deletedByUserId) => {
return result.affectedRows; return result.affectedRows;
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error(`[Model] 작업 보고서 삭제 오류 (id: ${reportId}):`, err); throw err;
throw new Error('데이터베이스에서 작업 보고서를 삭제하는 중 오류가 발생했습니다.');
} finally { } finally {
conn.release(); conn.release();
} }
@@ -1082,153 +961,108 @@ const removeReportById = async (reportId, deletedByUserId) => {
// ========== 마스터 데이터 CRUD 메서드들 ========== // ========== 마스터 데이터 CRUD 메서드들 ==========
/** /**
* 📝 작업 유형 생성 * 작업 유형 생성
*/ */
const createWorkType = async (data, callback) => { const createWorkType = async (data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, category } = data; const { name, description, category } = data;
const [result] = await db.query( const [result] = await db.query(
'INSERT INTO work_types (name, description, category) VALUES (?, ?, ?)', 'INSERT INTO work_types (name, description, category) VALUES (?, ?, ?)',
[name, description, category] [name, description, category]
); );
callback(null, { id: result.insertId, ...data }); return { id: result.insertId, ...data };
} catch (err) {
console.error('작업 유형 생성 오류:', err);
callback(err);
}
}; };
/** /**
* ✏️ 작업 유형 수정 * 작업 유형 수정
*/ */
const updateWorkType = async (id, data, callback) => { const updateWorkType = async (id, data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, category } = data; const { name, description, category } = data;
const [result] = await db.query( const [result] = await db.query(
'UPDATE work_types SET name = ?, description = ?, category = ? WHERE id = ?', 'UPDATE work_types SET name = ?, description = ?, category = ? WHERE id = ?',
[name, description, category, id] [name, description, category, id]
); );
callback(null, result); return result;
} catch (err) {
console.error('작업 유형 수정 오류:', err);
callback(err);
}
}; };
/** /**
* 🗑️ 작업 유형 삭제 * 작업 유형 삭제
*/ */
const deleteWorkType = async (id, callback) => { const deleteWorkType = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query('DELETE FROM work_types WHERE id = ?', [id]); const [result] = await db.query('DELETE FROM work_types WHERE id = ?', [id]);
callback(null, result); return result;
} catch (err) {
console.error('작업 유형 삭제 오류:', err);
callback(err);
}
}; };
/** /**
* 📝 작업 상태 생성 * 작업 상태 생성
*/ */
const createWorkStatus = async (data, callback) => { const createWorkStatus = async (data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, is_error } = data; const { name, description, is_error } = data;
const [result] = await db.query( const [result] = await db.query(
'INSERT INTO work_status_types (name, description, is_error) VALUES (?, ?, ?)', 'INSERT INTO work_status_types (name, description, is_error) VALUES (?, ?, ?)',
[name, description, is_error || 0] [name, description, is_error || 0]
); );
callback(null, { id: result.insertId, ...data }); return { id: result.insertId, ...data };
} catch (err) {
console.error('작업 상태 생성 오류:', err);
callback(err);
}
}; };
/** /**
* ✏️ 작업 상태 수정 * 작업 상태 수정
*/ */
const updateWorkStatus = async (id, data, callback) => { const updateWorkStatus = async (id, data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, is_error } = data; const { name, description, is_error } = data;
const [result] = await db.query( const [result] = await db.query(
'UPDATE work_status_types SET name = ?, description = ?, is_error = ? WHERE id = ?', 'UPDATE work_status_types SET name = ?, description = ?, is_error = ? WHERE id = ?',
[name, description, is_error || 0, id] [name, description, is_error || 0, id]
); );
callback(null, result); return result;
} catch (err) {
console.error('작업 상태 수정 오류:', err);
callback(err);
}
}; };
/** /**
* 🗑️ 작업 상태 삭제 * 작업 상태 삭제
*/ */
const deleteWorkStatus = async (id, callback) => { const deleteWorkStatus = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query('DELETE FROM work_status_types WHERE id = ?', [id]); const [result] = await db.query('DELETE FROM work_status_types WHERE id = ?', [id]);
callback(null, result); return result;
} catch (err) {
console.error('작업 상태 삭제 오류:', err);
callback(err);
}
}; };
/** /**
* 📝 오류 유형 생성 * 오류 유형 생성
*/ */
const createErrorType = async (data, callback) => { const createErrorType = async (data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, severity } = data; const { name, description, severity } = data;
const [result] = await db.query( const [result] = await db.query(
'INSERT INTO error_types (name, description, severity) VALUES (?, ?, ?)', 'INSERT INTO error_types (name, description, severity) VALUES (?, ?, ?)',
[name, description, severity || 'medium'] [name, description, severity || 'medium']
); );
callback(null, { id: result.insertId, ...data }); return { id: result.insertId, ...data };
} catch (err) {
console.error('오류 유형 생성 오류:', err);
callback(err);
}
}; };
/** /**
* ✏️ 오류 유형 수정 * 오류 유형 수정
*/ */
const updateErrorType = async (id, data, callback) => { const updateErrorType = async (id, data) => {
try {
const db = await getDb(); const db = await getDb();
const { name, description, severity } = data; const { name, description, severity } = data;
const [result] = await db.query( const [result] = await db.query(
'UPDATE error_types SET name = ?, description = ?, severity = ? WHERE id = ?', 'UPDATE error_types SET name = ?, description = ?, severity = ? WHERE id = ?',
[name, description, severity || 'medium', id] [name, description, severity || 'medium', id]
); );
callback(null, result); return result;
} catch (err) {
console.error('오류 유형 수정 오류:', err);
callback(err);
}
}; };
/** /**
* 🗑️ 오류 유형 삭제 * 오류 유형 삭제
*/ */
const deleteErrorType = async (id, callback) => { const deleteErrorType = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query('DELETE FROM error_types WHERE id = ?', [id]); const [result] = await db.query('DELETE FROM error_types WHERE id = ?', [id]);
callback(null, result); return result;
} catch (err) {
console.error('오류 유형 삭제 오류:', err);
callback(err);
}
}; };
@@ -1331,30 +1165,28 @@ const createFromTbmAssignment = async (reportData) => {
}; };
} catch (err) { } catch (err) {
await conn.rollback(); try { await conn.rollback(); } catch (e) {}
console.error('[Model] TBM 작업보고서 생성 중 오류 발생:', err); throw err;
throw new Error('TBM 작업보고서 생성 중 오류가 발생했습니다.');
} finally { } finally {
conn.release(); conn.release();
} }
}; };
// 모든 함수 내보내기 (Promise 기반 함수 위주로 재구성) // 모든 함수 내보내기
module.exports = { module.exports = {
// 새로 추가된 V2 함수 (Promise 기반) // V2 함수
createReportEntries, createReportEntries,
getReportsWithOptions, getReportsWithOptions,
updateReportById, updateReportById,
removeReportById, removeReportById,
createFromTbmAssignment, createFromTbmAssignment,
// Promise 기반으로 리팩토링된 함수 // 통계/요약
getStatistics, getStatistics,
getSummaryByDate, getSummaryByDate,
getSummaryByWorker, getSummaryByWorker,
// 아직 리팩토링되지 않았지만 필요한 기존 함수들... // 마스터 데이터 조회
// (점진적으로 아래 함수들도 Promise 기반으로 전환해야 함)
getAllWorkTypes, getAllWorkTypes,
getAllWorkStatusTypes, getAllWorkStatusTypes,
getAllErrorTypes, getAllErrorTypes,
@@ -1369,6 +1201,8 @@ module.exports = {
createErrorType, createErrorType,
updateErrorType, updateErrorType,
deleteErrorType, deleteErrorType,
// 레거시 함수 (콜백 제거 완료)
createDailyReport, createDailyReport,
getMyAccumulatedHours, getMyAccumulatedHours,
getAccumulatedReportsByDate, getAccumulatedReportsByDate,

View File

@@ -4,8 +4,7 @@ const notificationModel = require('./notificationModel');
const EquipmentModel = { const EquipmentModel = {
// CREATE - 설비 생성 // CREATE - 설비 생성
create: async (equipmentData, callback) => { create: async (equipmentData) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const query = `
INSERT INTO equipments ( INSERT INTO equipments (
@@ -37,18 +36,14 @@ const EquipmentModel = {
]; ];
const [result] = await db.query(query, values); const [result] = await db.query(query, values);
callback(null, { return {
equipment_id: result.insertId, equipment_id: result.insertId,
...equipmentData ...equipmentData
}); };
} catch (error) {
callback(error);
}
}, },
// READ ALL - 모든 설비 조회 (필터링 옵션 포함) // READ ALL - 모든 설비 조회 (필터링 옵션 포함)
getAll: async (filters, callback) => { getAll: async (filters) => {
try {
const db = await getDb(); const db = await getDb();
let query = ` let query = `
SELECT SELECT
@@ -63,25 +58,21 @@ const EquipmentModel = {
const values = []; const values = [];
// 필터링: 작업장 ID
if (filters.workplace_id) { if (filters.workplace_id) {
query += ' AND e.workplace_id = ?'; query += ' AND e.workplace_id = ?';
values.push(filters.workplace_id); values.push(filters.workplace_id);
} }
// 필터링: 설비 유형
if (filters.equipment_type) { if (filters.equipment_type) {
query += ' AND e.equipment_type = ?'; query += ' AND e.equipment_type = ?';
values.push(filters.equipment_type); values.push(filters.equipment_type);
} }
// 필터링: 상태
if (filters.status) { if (filters.status) {
query += ' AND e.status = ?'; query += ' AND e.status = ?';
values.push(filters.status); values.push(filters.status);
} }
// 필터링: 검색어 (설비명, 설비코드)
if (filters.search) { if (filters.search) {
query += ' AND (e.equipment_name LIKE ? OR e.equipment_code LIKE ?)'; query += ' AND (e.equipment_name LIKE ? OR e.equipment_code LIKE ?)';
const searchTerm = `%${filters.search}%`; const searchTerm = `%${filters.search}%`;
@@ -91,58 +82,44 @@ const EquipmentModel = {
query += ' ORDER BY e.equipment_code ASC'; query += ' ORDER BY e.equipment_code ASC';
const [rows] = await db.query(query, values); const [rows] = await db.query(query, values);
callback(null, rows); return rows;
} catch (error) {
callback(error);
}
}, },
// READ ONE - 특정 설비 조회 // READ ONE - 특정 설비 조회
getById: async (equipmentId, callback) => { getById: async (equipmentId) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
e.*, e.*,
w.workplace_name, w.workplace_name,
wc.category_name wc.category_name
FROM equipments e FROM equipments e
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
WHERE e.equipment_id = ? WHERE e.equipment_id = ?`,
`; [equipmentId]
);
const [rows] = await db.query(query, [equipmentId]); return rows[0];
callback(null, rows[0]);
} catch (error) {
callback(error);
}
}, },
// READ BY WORKPLACE - 특정 작업장의 설비 조회 // READ BY WORKPLACE - 특정 작업장의 설비 조회
getByWorkplace: async (workplaceId, callback) => { getByWorkplace: async (workplaceId) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT e.* `SELECT e.*
FROM equipments e FROM equipments e
WHERE e.workplace_id = ? WHERE e.workplace_id = ?
ORDER BY e.equipment_code ASC ORDER BY e.equipment_code ASC`,
`; [workplaceId]
);
const [rows] = await db.query(query, [workplaceId]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// READ ACTIVE - 활성 설비만 조회 // READ ACTIVE - 활성 설비만 조회
getActive: async (callback) => { getActive: async () => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
e.*, e.*,
w.workplace_name, w.workplace_name,
wc.category_name wc.category_name
@@ -150,43 +127,14 @@ const EquipmentModel = {
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
WHERE e.status = 'active' WHERE e.status = 'active'
ORDER BY e.equipment_code ASC ORDER BY e.equipment_code ASC`
`; );
return rows;
const [rows] = await db.query(query);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// UPDATE - 설비 수정 // UPDATE - 설비 수정
update: async (equipmentId, equipmentData, callback) => { update: async (equipmentId, equipmentData) => {
try {
const db = await getDb(); const db = await getDb();
const query = `
UPDATE equipments SET
equipment_code = ?,
equipment_name = ?,
equipment_type = ?,
model_name = ?,
manufacturer = ?,
supplier = ?,
purchase_price = ?,
installation_date = ?,
serial_number = ?,
specifications = ?,
status = ?,
notes = ?,
workplace_id = ?,
map_x_percent = ?,
map_y_percent = ?,
map_width_percent = ?,
map_height_percent = ?,
updated_at = NOW()
WHERE equipment_id = ?
`;
const values = [ const values = [
equipmentData.equipment_code, equipmentData.equipment_code,
equipmentData.equipment_name, equipmentData.equipment_name,
@@ -208,22 +156,40 @@ const EquipmentModel = {
equipmentId equipmentId
]; ];
const [result] = await db.query(query, values); const [result] = await db.query(
`UPDATE equipments SET
equipment_code = ?,
equipment_name = ?,
equipment_type = ?,
model_name = ?,
manufacturer = ?,
supplier = ?,
purchase_price = ?,
installation_date = ?,
serial_number = ?,
specifications = ?,
status = ?,
notes = ?,
workplace_id = ?,
map_x_percent = ?,
map_y_percent = ?,
map_width_percent = ?,
map_height_percent = ?,
updated_at = NOW()
WHERE equipment_id = ?`,
values
);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return callback(new Error('Equipment not found')); throw new Error('Equipment not found');
}
callback(null, { equipment_id: equipmentId, ...equipmentData });
} catch (error) {
callback(error);
} }
return { equipment_id: equipmentId, ...equipmentData };
}, },
// UPDATE MAP POSITION - 지도상 위치 업데이트 (선택적으로 workplace_id도 업데이트) // UPDATE MAP POSITION - 지도상 위치 업데이트
updateMapPosition: async (equipmentId, positionData, callback) => { updateMapPosition: async (equipmentId, positionData) => {
try {
const db = await getDb(); const db = await getDb();
// workplace_id가 포함된 경우 함께 업데이트
const hasWorkplaceId = positionData.workplace_id !== undefined; const hasWorkplaceId = positionData.workplace_id !== undefined;
const query = hasWorkplaceId ? ` const query = hasWorkplaceId ? `
@@ -262,33 +228,23 @@ const EquipmentModel = {
const [result] = await db.query(query, values); const [result] = await db.query(query, values);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return callback(new Error('Equipment not found')); throw new Error('Equipment not found');
}
callback(null, { equipment_id: equipmentId, ...positionData });
} catch (error) {
callback(error);
} }
return { equipment_id: equipmentId, ...positionData };
}, },
// DELETE - 설비 삭제 // DELETE - 설비 삭제
delete: async (equipmentId, callback) => { delete: async (equipmentId) => {
try {
const db = await getDb(); const db = await getDb();
const query = 'DELETE FROM equipments WHERE equipment_id = ?'; const [result] = await db.query('DELETE FROM equipments WHERE equipment_id = ?', [equipmentId]);
const [result] = await db.query(query, [equipmentId]);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return callback(new Error('Equipment not found')); throw new Error('Equipment not found');
}
callback(null, { equipment_id: equipmentId });
} catch (error) {
callback(error);
} }
return { equipment_id: equipmentId };
}, },
// CHECK DUPLICATE CODE - 설비 코드 중복 확인 // CHECK DUPLICATE CODE - 설비 코드 중복 확인
checkDuplicateCode: async (equipmentCode, excludeEquipmentId, callback) => { checkDuplicateCode: async (equipmentCode, excludeEquipmentId) => {
try {
const db = await getDb(); const db = await getDb();
let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?'; let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?';
const values = [equipmentCode]; const values = [equipmentCode];
@@ -299,48 +255,35 @@ const EquipmentModel = {
} }
const [rows] = await db.query(query, values); const [rows] = await db.query(query, values);
callback(null, rows.length > 0); return rows.length > 0;
} catch (error) {
callback(error);
}
}, },
// GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회 // GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회
getEquipmentTypes: async (callback) => { getEquipmentTypes: async () => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT DISTINCT equipment_type `SELECT DISTINCT equipment_type
FROM equipments FROM equipments
WHERE equipment_type IS NOT NULL WHERE equipment_type IS NOT NULL
ORDER BY equipment_type ASC ORDER BY equipment_type ASC`
`; );
return rows.map(row => row.equipment_type);
const [rows] = await db.query(query);
callback(null, rows.map(row => row.equipment_type));
} catch (error) {
callback(error);
}
}, },
// GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 (TKP-001 형식) // GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성
getNextEquipmentCode: async (prefix = 'TKP', callback) => { getNextEquipmentCode: async (prefix = 'TKP') => {
try {
const db = await getDb(); const db = await getDb();
// 해당 접두사로 시작하는 가장 큰 번호 찾기 const [rows] = await db.query(
const query = ` `SELECT equipment_code
SELECT equipment_code
FROM equipments FROM equipments
WHERE equipment_code LIKE ? WHERE equipment_code LIKE ?
ORDER BY equipment_code DESC ORDER BY equipment_code DESC
LIMIT 1 LIMIT 1`,
`; [`${prefix}-%`]
);
const [rows] = await db.query(query, [`${prefix}-%`]);
let nextNumber = 1; let nextNumber = 1;
if (rows.length > 0) { if (rows.length > 0) {
// TKP-001 형식에서 숫자 부분 추출
const lastCode = rows[0].equipment_code; const lastCode = rows[0].equipment_code;
const match = lastCode.match(new RegExp(`^${prefix}-(\\d+)$`)); const match = lastCode.match(new RegExp(`^${prefix}-(\\d+)$`));
if (match) { if (match) {
@@ -348,28 +291,15 @@ const EquipmentModel = {
} }
} }
// 3자리로 패딩 (001, 002, ...) return `${prefix}-${String(nextNumber).padStart(3, '0')}`;
const nextCode = `${prefix}-${String(nextNumber).padStart(3, '0')}`;
callback(null, nextCode);
} catch (error) {
callback(error);
}
}, },
// ========================================== // ==========================================
// 설비 사진 관리 // 설비 사진 관리
// ========================================== // ==========================================
// ADD PHOTO - 설비 사진 추가 addPhoto: async (equipmentId, photoData) => {
addPhoto: async (equipmentId, photoData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = `
INSERT INTO equipment_photos (
equipment_id, photo_path, description, display_order, uploaded_by
) VALUES (?, ?, ?, ?, ?)
`;
const values = [ const values = [
equipmentId, equipmentId,
photoData.photo_path, photoData.photo_path,
@@ -378,72 +308,59 @@ const EquipmentModel = {
photoData.uploaded_by || null photoData.uploaded_by || null
]; ];
const [result] = await db.query(query, values); const [result] = await db.query(
callback(null, { `INSERT INTO equipment_photos (
equipment_id, photo_path, description, display_order, uploaded_by
) VALUES (?, ?, ?, ?, ?)`,
values
);
return {
photo_id: result.insertId, photo_id: result.insertId,
equipment_id: equipmentId, equipment_id: equipmentId,
...photoData ...photoData
}); };
} catch (error) {
callback(error);
}
}, },
// GET PHOTOS - 설비 사진 조회 getPhotos: async (equipmentId) => {
getPhotos: async (equipmentId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT ep.*, u.name AS uploaded_by_name `SELECT ep.*, u.name AS uploaded_by_name
FROM equipment_photos ep FROM equipment_photos ep
LEFT JOIN users u ON ep.uploaded_by = u.user_id LEFT JOIN users u ON ep.uploaded_by = u.user_id
WHERE ep.equipment_id = ? WHERE ep.equipment_id = ?
ORDER BY ep.display_order ASC, ep.created_at ASC ORDER BY ep.display_order ASC, ep.created_at ASC`,
`; [equipmentId]
);
const [rows] = await db.query(query, [equipmentId]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// DELETE PHOTO - 설비 사진 삭제 deletePhoto: async (photoId) => {
deletePhoto: async (photoId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 먼저 사진 정보 조회 (파일 삭제용)
const [photo] = await db.query( const [photo] = await db.query(
'SELECT photo_path FROM equipment_photos WHERE photo_id = ?', 'SELECT photo_path FROM equipment_photos WHERE photo_id = ?',
[photoId] [photoId]
); );
const query = 'DELETE FROM equipment_photos WHERE photo_id = ?'; const [result] = await db.query('DELETE FROM equipment_photos WHERE photo_id = ?', [photoId]);
const [result] = await db.query(query, [photoId]);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return callback(new Error('Photo not found')); throw new Error('Photo not found');
} }
callback(null, { photo_id: photoId, photo_path: photo[0]?.photo_path }); return { photo_id: photoId, photo_path: photo[0]?.photo_path };
} catch (error) {
callback(error);
}
}, },
// ========================================== // ==========================================
// 설비 임시 이동 // 설비 임시 이동
// ========================================== // ==========================================
// MOVE TEMPORARILY - 설비 임시 이동 moveTemporarily: async (equipmentId, moveData) => {
moveTemporarily: async (equipmentId, moveData, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 1. 설비 현재 위치 업데이트 const [result] = await db.query(
const updateQuery = ` `UPDATE equipments SET
UPDATE equipments SET
current_workplace_id = ?, current_workplace_id = ?,
current_map_x_percent = ?, current_map_x_percent = ?,
current_map_y_percent = ?, current_map_y_percent = ?,
@@ -453,10 +370,8 @@ const EquipmentModel = {
moved_at = NOW(), moved_at = NOW(),
moved_by = ?, moved_by = ?,
updated_at = NOW() updated_at = NOW()
WHERE equipment_id = ? WHERE equipment_id = ?`,
`; [
const updateValues = [
moveData.target_workplace_id, moveData.target_workplace_id,
moveData.target_x_percent, moveData.target_x_percent,
moveData.target_y_percent, moveData.target_y_percent,
@@ -464,26 +379,22 @@ const EquipmentModel = {
moveData.target_height_percent || null, moveData.target_height_percent || null,
moveData.moved_by || null, moveData.moved_by || null,
equipmentId equipmentId
]; ]
);
const [result] = await db.query(updateQuery, updateValues);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return callback(new Error('Equipment not found')); throw new Error('Equipment not found');
} }
// 2. 이동 이력 기록 await db.query(
const logQuery = ` `INSERT INTO equipment_move_logs (
INSERT INTO equipment_move_logs (
equipment_id, move_type, equipment_id, move_type,
from_workplace_id, to_workplace_id, from_workplace_id, to_workplace_id,
from_x_percent, from_y_percent, from_x_percent, from_y_percent,
to_x_percent, to_y_percent, to_x_percent, to_y_percent,
reason, moved_by reason, moved_by
) VALUES (?, 'temporary', ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, 'temporary', ?, ?, ?, ?, ?, ?, ?, ?)`,
`; [
await db.query(logQuery, [
equipmentId, equipmentId,
moveData.from_workplace_id || null, moveData.from_workplace_id || null,
moveData.target_workplace_id, moveData.target_workplace_id,
@@ -493,32 +404,26 @@ const EquipmentModel = {
moveData.target_y_percent, moveData.target_y_percent,
moveData.reason || null, moveData.reason || null,
moveData.moved_by || null moveData.moved_by || null
]); ]
);
callback(null, { equipment_id: equipmentId, moved: true }); return { equipment_id: equipmentId, moved: true };
} catch (error) {
callback(error);
}
}, },
// RETURN TO ORIGINAL - 설비 원위치 복귀 returnToOriginal: async (equipmentId, userId) => {
returnToOriginal: async (equipmentId, userId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 1. 현재 임시 위치 정보 조회
const [equipment] = await db.query( const [equipment] = await db.query(
'SELECT current_workplace_id, current_map_x_percent, current_map_y_percent FROM equipments WHERE equipment_id = ?', 'SELECT current_workplace_id, current_map_x_percent, current_map_y_percent FROM equipments WHERE equipment_id = ?',
[equipmentId] [equipmentId]
); );
if (!equipment[0]) { if (!equipment[0]) {
return callback(new Error('Equipment not found')); throw new Error('Equipment not found');
} }
// 2. 임시 위치 필드 초기화 await db.query(
const updateQuery = ` `UPDATE equipments SET
UPDATE equipments SET
current_workplace_id = NULL, current_workplace_id = NULL,
current_map_x_percent = NULL, current_map_x_percent = NULL,
current_map_y_percent = NULL, current_map_y_percent = NULL,
@@ -528,40 +433,32 @@ const EquipmentModel = {
moved_at = NULL, moved_at = NULL,
moved_by = NULL, moved_by = NULL,
updated_at = NOW() updated_at = NOW()
WHERE equipment_id = ? WHERE equipment_id = ?`,
`; [equipmentId]
);
await db.query(updateQuery, [equipmentId]); await db.query(
`INSERT INTO equipment_move_logs (
// 3. 복귀 이력 기록
const logQuery = `
INSERT INTO equipment_move_logs (
equipment_id, move_type, equipment_id, move_type,
from_workplace_id, from_x_percent, from_y_percent, from_workplace_id, from_x_percent, from_y_percent,
reason, moved_by reason, moved_by
) VALUES (?, 'return', ?, ?, ?, '원위치 복귀', ?) ) VALUES (?, 'return', ?, ?, ?, '원위치 복귀', ?)`,
`; [
await db.query(logQuery, [
equipmentId, equipmentId,
equipment[0].current_workplace_id, equipment[0].current_workplace_id,
equipment[0].current_map_x_percent, equipment[0].current_map_x_percent,
equipment[0].current_map_y_percent, equipment[0].current_map_y_percent,
userId || null userId || null
]); ]
);
callback(null, { equipment_id: equipmentId, returned: true }); return { equipment_id: equipmentId, returned: true };
} catch (error) {
callback(error);
}
}, },
// GET TEMPORARILY MOVED - 임시 이동된 설비 목록 getTemporarilyMoved: async () => {
getTemporarilyMoved: async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
e.*, e.*,
w_orig.workplace_name AS original_workplace_name, w_orig.workplace_name AS original_workplace_name,
w_curr.workplace_name AS current_workplace_name, w_curr.workplace_name AS current_workplace_name,
@@ -571,22 +468,15 @@ const EquipmentModel = {
LEFT JOIN workplaces w_curr ON e.current_workplace_id = w_curr.workplace_id LEFT JOIN workplaces w_curr ON e.current_workplace_id = w_curr.workplace_id
LEFT JOIN users u ON e.moved_by = u.user_id LEFT JOIN users u ON e.moved_by = u.user_id
WHERE e.is_temporarily_moved = TRUE WHERE e.is_temporarily_moved = TRUE
ORDER BY e.moved_at DESC ORDER BY e.moved_at DESC`
`; );
return rows;
const [rows] = await db.query(query);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// GET MOVE LOGS - 설비 이동 이력 조회 getMoveLogs: async (equipmentId) => {
getMoveLogs: async (equipmentId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
eml.*, eml.*,
w_from.workplace_name AS from_workplace_name, w_from.workplace_name AS from_workplace_name,
w_to.workplace_name AS to_workplace_name, w_to.workplace_name AS to_workplace_name,
@@ -596,34 +486,25 @@ const EquipmentModel = {
LEFT JOIN workplaces w_to ON eml.to_workplace_id = w_to.workplace_id LEFT JOIN workplaces w_to ON eml.to_workplace_id = w_to.workplace_id
LEFT JOIN users u ON eml.moved_by = u.user_id LEFT JOIN users u ON eml.moved_by = u.user_id
WHERE eml.equipment_id = ? WHERE eml.equipment_id = ?
ORDER BY eml.moved_at DESC ORDER BY eml.moved_at DESC`,
`; [equipmentId]
);
const [rows] = await db.query(query, [equipmentId]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// ========================================== // ==========================================
// 설비 외부 반출/반입 // 설비 외부 반출/반입
// ========================================== // ==========================================
// EXPORT EQUIPMENT - 설비 외부 반출 exportEquipment: async (exportData) => {
exportEquipment: async (exportData, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 1. 반출 로그 생성 const [logResult] = await db.query(
const logQuery = ` `INSERT INTO equipment_external_logs (
INSERT INTO equipment_external_logs (
equipment_id, log_type, export_date, expected_return_date, equipment_id, log_type, export_date, expected_return_date,
destination, reason, notes, exported_by destination, reason, notes, exported_by
) VALUES (?, 'export', ?, ?, ?, ?, ?, ?) ) VALUES (?, 'export', ?, ?, ?, ?, ?, ?)`,
`; [
const logValues = [
exportData.equipment_id, exportData.equipment_id,
exportData.export_date || new Date().toISOString().slice(0, 10), exportData.export_date || new Date().toISOString().slice(0, 10),
exportData.expected_return_date || null, exportData.expected_return_date || null,
@@ -631,45 +512,36 @@ const EquipmentModel = {
exportData.reason || null, exportData.reason || null,
exportData.notes || null, exportData.notes || null,
exportData.exported_by || null exportData.exported_by || null
]; ]
);
const [logResult] = await db.query(logQuery, logValues);
// 2. 설비 상태 업데이트
const status = exportData.is_repair ? 'repair_external' : 'external'; const status = exportData.is_repair ? 'repair_external' : 'external';
await db.query( await db.query(
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
[status, exportData.equipment_id] [status, exportData.equipment_id]
); );
callback(null, { return {
log_id: logResult.insertId, log_id: logResult.insertId,
equipment_id: exportData.equipment_id, equipment_id: exportData.equipment_id,
exported: true exported: true
}); };
} catch (error) {
callback(error);
}
}, },
// RETURN EQUIPMENT - 설비 반입 (외부에서 복귀) returnEquipment: async (logId, returnData) => {
returnEquipment: async (logId, returnData, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 1. 반출 로그 조회
const [logs] = await db.query( const [logs] = await db.query(
'SELECT equipment_id FROM equipment_external_logs WHERE log_id = ?', 'SELECT equipment_id FROM equipment_external_logs WHERE log_id = ?',
[logId] [logId]
); );
if (!logs[0]) { if (!logs[0]) {
return callback(new Error('Export log not found')); throw new Error('Export log not found');
} }
const equipmentId = logs[0].equipment_id; const equipmentId = logs[0].equipment_id;
// 2. 반출 로그 업데이트
await db.query( await db.query(
`UPDATE equipment_external_logs SET `UPDATE equipment_external_logs SET
actual_return_date = ?, actual_return_date = ?,
@@ -685,28 +557,22 @@ const EquipmentModel = {
] ]
); );
// 3. 설비 상태 복원
await db.query( await db.query(
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
[returnData.new_status || 'active', equipmentId] [returnData.new_status || 'active', equipmentId]
); );
callback(null, { return {
log_id: logId, log_id: logId,
equipment_id: equipmentId, equipment_id: equipmentId,
returned: true returned: true
}); };
} catch (error) {
callback(error);
}
}, },
// GET EXTERNAL LOGS - 설비 외부 반출 이력 조회 getExternalLogs: async (equipmentId) => {
getExternalLogs: async (equipmentId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
eel.*, eel.*,
u_exp.name AS exported_by_name, u_exp.name AS exported_by_name,
u_ret.name AS returned_by_name u_ret.name AS returned_by_name
@@ -714,22 +580,16 @@ const EquipmentModel = {
LEFT JOIN users u_exp ON eel.exported_by = u_exp.user_id LEFT JOIN users u_exp ON eel.exported_by = u_exp.user_id
LEFT JOIN users u_ret ON eel.returned_by = u_ret.user_id LEFT JOIN users u_ret ON eel.returned_by = u_ret.user_id
WHERE eel.equipment_id = ? WHERE eel.equipment_id = ?
ORDER BY eel.created_at DESC ORDER BY eel.created_at DESC`,
`; [equipmentId]
);
const [rows] = await db.query(query, [equipmentId]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// GET EXPORTED EQUIPMENTS - 현재 외부 반출 중인 설비 목록 getExportedEquipments: async () => {
getExportedEquipments: async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
e.*, e.*,
w.workplace_name, w.workplace_name,
eel.export_date, eel.export_date,
@@ -748,37 +608,28 @@ const EquipmentModel = {
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
LEFT JOIN users u ON eel.exported_by = u.user_id LEFT JOIN users u ON eel.exported_by = u.user_id
WHERE e.status IN ('external', 'repair_external') WHERE e.status IN ('external', 'repair_external')
ORDER BY eel.export_date DESC ORDER BY eel.export_date DESC`
`; );
return rows;
const [rows] = await db.query(query);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// ========================================== // ==========================================
// 설비 수리 신청 (work_issue_reports 연동) // 설비 수리 신청
// ========================================== // ==========================================
// CREATE REPAIR REQUEST - 수리 신청 (신고 시스템 활용) createRepairRequest: async (requestData) => {
createRepairRequest: async (requestData, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 설비 수리 카테고리 ID 조회
const [categories] = await db.query( const [categories] = await db.query(
"SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리' LIMIT 1" "SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리' LIMIT 1"
); );
if (!categories[0]) { if (!categories[0]) {
return callback(new Error('설비 수리 카테고리가 없습니다')); throw new Error('설비 수리 카테고리가 없습니다');
} }
const categoryId = categories[0].category_id; const categoryId = categories[0].category_id;
// 항목 ID 조회 (지정된 항목이 없으면 첫번째 항목 사용)
let itemId = requestData.item_id; let itemId = requestData.item_id;
if (!itemId) { if (!itemId) {
const [items] = await db.query( const [items] = await db.query(
@@ -788,21 +639,17 @@ const EquipmentModel = {
itemId = items[0]?.item_id; itemId = items[0]?.item_id;
} }
// 사진 경로 분리 (최대 5장)
const photos = requestData.photo_paths || []; const photos = requestData.photo_paths || [];
// work_issue_reports에 삽입 const [result] = await db.query(
const query = ` `INSERT INTO work_issue_reports (
INSERT INTO work_issue_reports (
reporter_id, issue_category_id, issue_item_id, reporter_id, issue_category_id, issue_item_id,
workplace_id, equipment_id, workplace_id, equipment_id,
additional_description, additional_description,
photo_path1, photo_path2, photo_path3, photo_path4, photo_path5, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5,
status status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'reported') ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'reported')`,
`; [
const values = [
requestData.reported_by || null, requestData.reported_by || null,
categoryId, categoryId,
itemId, itemId,
@@ -814,17 +661,14 @@ const EquipmentModel = {
photos[2] || null, photos[2] || null,
photos[3] || null, photos[3] || null,
photos[4] || null photos[4] || null
]; ]
);
const [result] = await db.query(query, values);
// 설비 상태를 repair_needed로 업데이트
await db.query( await db.query(
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
['repair_needed', requestData.equipment_id] ['repair_needed', requestData.equipment_id]
); );
// 알림 생성
try { try {
await notificationModel.createRepairNotification({ await notificationModel.createRepairNotification({
equipment_id: requestData.equipment_id, equipment_id: requestData.equipment_id,
@@ -834,26 +678,20 @@ const EquipmentModel = {
created_by: requestData.reported_by created_by: requestData.reported_by
}); });
} catch (notifError) { } catch (notifError) {
console.error('알림 생성 실패:', notifError);
// 알림 생성 실패해도 수리 신청은 성공으로 처리 // 알림 생성 실패해도 수리 신청은 성공으로 처리
} }
callback(null, { return {
report_id: result.insertId, report_id: result.insertId,
equipment_id: requestData.equipment_id, equipment_id: requestData.equipment_id,
created: true created: true
}); };
} catch (error) {
callback(error);
}
}, },
// GET REPAIR HISTORY - 설비 수리 이력 조회 getRepairHistory: async (equipmentId) => {
getRepairHistory: async (equipmentId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(
SELECT `SELECT
wir.*, wir.*,
irc.category_name, irc.category_name,
iri.item_name, iri.item_name,
@@ -867,82 +705,59 @@ const EquipmentModel = {
LEFT JOIN users u_res ON wir.resolved_by = u_res.user_id LEFT JOIN users u_res ON wir.resolved_by = u_res.user_id
LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id
WHERE wir.equipment_id = ? WHERE wir.equipment_id = ?
ORDER BY wir.created_at DESC ORDER BY wir.created_at DESC`,
`; [equipmentId]
);
const [rows] = await db.query(query, [equipmentId]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// GET REPAIR CATEGORIES - 설비 수리 항목 목록 조회 getRepairCategories: async () => {
getRepairCategories: async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query(
// 설비 수리 카테고리의 항목들 조회 `SELECT iri.item_id, iri.item_name, iri.description, iri.severity
const query = `
SELECT iri.item_id, iri.item_name, iri.description, iri.severity
FROM issue_report_items iri FROM issue_report_items iri
INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id
WHERE irc.category_name = '설비 수리' AND iri.is_active = 1 WHERE irc.category_name = '설비 수리' AND iri.is_active = 1
ORDER BY iri.display_order ASC ORDER BY iri.display_order ASC`
`; );
return rows;
const [rows] = await db.query(query);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
// ADD REPAIR CATEGORY - 새 수리 항목 추가 addRepairCategory: async (itemName) => {
addRepairCategory: async (itemName, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 설비 수리 카테고리 ID 조회
const [categories] = await db.query( const [categories] = await db.query(
"SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리'" "SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리'"
); );
if (categories.length === 0) { if (categories.length === 0) {
return callback(new Error('설비 수리 카테고리가 없습니다.')); throw new Error('설비 수리 카테고리가 없습니다.');
} }
const categoryId = categories[0].category_id; const categoryId = categories[0].category_id;
// 중복 확인
const [existing] = await db.query( const [existing] = await db.query(
'SELECT item_id FROM issue_report_items WHERE category_id = ? AND item_name = ?', 'SELECT item_id FROM issue_report_items WHERE category_id = ? AND item_name = ?',
[categoryId, itemName] [categoryId, itemName]
); );
if (existing.length > 0) { if (existing.length > 0) {
// 이미 존재하면 해당 ID 반환 return { item_id: existing[0].item_id, item_name: itemName, isNew: false };
return callback(null, { item_id: existing[0].item_id, item_name: itemName, isNew: false });
} }
// 다음 display_order 구하기
const [maxOrder] = await db.query( const [maxOrder] = await db.query(
'SELECT MAX(display_order) as max_order FROM issue_report_items WHERE category_id = ?', 'SELECT MAX(display_order) as max_order FROM issue_report_items WHERE category_id = ?',
[categoryId] [categoryId]
); );
const nextOrder = (maxOrder[0].max_order || 0) + 1; const nextOrder = (maxOrder[0].max_order || 0) + 1;
// 새 항목 추가
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO issue_report_items (category_id, item_name, display_order, is_active) `INSERT INTO issue_report_items (category_id, item_name, display_order, is_active)
VALUES (?, ?, ?, 1)`, VALUES (?, ?, ?, 1)`,
[categoryId, itemName, nextOrder] [categoryId, itemName, nextOrder]
); );
callback(null, { item_id: result.insertId, item_name: itemName, isNew: true }); return { item_id: result.insertId, item_name: itemName, isNew: true };
} catch (error) {
callback(error);
}
} }
}; };

View File

@@ -1,160 +0,0 @@
// models/pageAccessModel.js
const db = require('../db/connection');
const PageAccessModel = {
// 사용자의 페이지 권한 조회
getUserPageAccess: (userId, callback) => {
const sql = `
SELECT
p.id,
p.page_key,
p.page_name,
p.page_path,
p.category,
p.is_admin_only,
COALESCE(upa.can_access, 0) as can_access,
upa.granted_at,
upa.granted_by,
granter.username as granted_by_username
FROM pages p
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
LEFT JOIN users granter ON upa.granted_by = granter.user_id
WHERE p.is_admin_only = 0
ORDER BY p.category, p.display_order
`;
db.query(sql, [userId], callback);
},
// 모든 페이지 목록 조회
getAllPages: (callback) => {
const sql = `
SELECT
id,
page_key,
page_name,
page_path,
category,
description,
is_admin_only,
display_order
FROM pages
WHERE is_admin_only = 0
ORDER BY category, display_order
`;
db.query(sql, callback);
},
// 페이지 권한 부여
grantPageAccess: (userId, pageId, grantedBy, callback) => {
const sql = `
INSERT INTO user_page_access (user_id, page_id, can_access, granted_by, granted_at)
VALUES (?, ?, 1, ?, NOW())
ON DUPLICATE KEY UPDATE
can_access = 1,
granted_by = ?,
granted_at = NOW()
`;
db.query(sql, [userId, pageId, grantedBy, grantedBy], callback);
},
// 페이지 권한 회수
revokePageAccess: (userId, pageId, callback) => {
const sql = `
DELETE FROM user_page_access
WHERE user_id = ? AND page_id = ?
`;
db.query(sql, [userId, pageId], callback);
},
// 여러 페이지 권한 일괄 설정
setUserPageAccess: (userId, pageIds, grantedBy, callback) => {
db.beginTransaction((err) => {
if (err) return callback(err);
// 기존 권한 모두 삭제
const deleteSql = 'DELETE FROM user_page_access WHERE user_id = ?';
db.query(deleteSql, [userId], (err) => {
if (err) {
return db.rollback(() => callback(err));
}
// 새 권한이 없으면 커밋하고 종료
if (!pageIds || pageIds.length === 0) {
return db.commit((err) => {
if (err) return db.rollback(() => callback(err));
callback(null, { affectedRows: 0 });
});
}
// 새 권한 추가
const values = pageIds.map(pageId => [userId, pageId, 1, grantedBy]);
const insertSql = `
INSERT INTO user_page_access (user_id, page_id, can_access, granted_by, granted_at)
VALUES ?
`;
db.query(insertSql, [values], (err, result) => {
if (err) {
return db.rollback(() => callback(err));
}
db.commit((err) => {
if (err) return db.rollback(() => callback(err));
callback(null, result);
});
});
});
});
},
// 특정 페이지 접근 권한 확인
checkPageAccess: (userId, pageKey, callback) => {
const sql = `
SELECT
COALESCE(upa.can_access, 0) as can_access,
p.is_admin_only
FROM pages p
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
WHERE p.page_key = ?
`;
db.query(sql, [userId, pageKey], (err, results) => {
if (err) return callback(err);
if (results.length === 0) return callback(null, { can_access: false });
callback(null, results[0]);
});
},
// 계정이 있는 작업자 목록 조회 (권한 관리용)
getUsersWithAccounts: (callback) => {
const sql = `
SELECT
u.user_id,
u.username,
u.name,
u.role_id,
r.name as role_name,
u.worker_id,
w.worker_name,
w.job_type,
COUNT(upa.page_id) as granted_pages_count
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
LEFT JOIN workers w ON u.worker_id = w.worker_id
LEFT JOIN user_page_access upa ON u.user_id = upa.user_id AND upa.can_access = 1
WHERE u.is_active = 1
AND u.role_id IN (4, 5)
GROUP BY u.user_id
ORDER BY w.worker_name, u.username
`;
db.query(sql, callback);
}
};
module.exports = PageAccessModel;

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,10 @@ const TbmTransferModel = {
* 작업자 이동 실행 (보내기/빼오기) * 작업자 이동 실행 (보내기/빼오기)
* 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT * 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT
*/ */
createTransfer: async (transferData, callback) => { async createTransfer(transferData) {
let conn;
try {
const db = await getDb(); const db = await getDb();
conn = await db.getConnection(); const conn = await db.getConnection();
try {
await conn.beginTransaction(); await conn.beginTransaction();
const { const {
@@ -27,8 +26,7 @@ const TbmTransferModel = {
if (sourceRows.length === 0) { if (sourceRows.length === 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' };
return callback(null, { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' });
} }
const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours); const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
@@ -36,8 +34,7 @@ const TbmTransferModel = {
if (newSourceHours < 0) { if (newSourceHours < 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' };
return callback(null, { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' });
} }
await conn.query( await conn.query(
@@ -52,14 +49,12 @@ const TbmTransferModel = {
); );
if (destRows.length > 0) { if (destRows.length > 0) {
// 이미 있으면 시간만 추가
const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours); const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
await conn.query( await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
[existingHours + parseFloat(hours), dest_session_id, worker_id] [existingHours + parseFloat(hours), dest_session_id, worker_id]
); );
} else { } else {
// 새로 INSERT
await conn.query( await conn.query(
`INSERT INTO tbm_team_assignments `INSERT INTO tbm_team_assignments
(session_id, worker_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present) (session_id, worker_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present)
@@ -89,7 +84,6 @@ const TbmTransferModel = {
const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0; const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0;
await conn.commit(); await conn.commit();
conn.release();
const result = { const result = {
success: true, success: true,
@@ -101,24 +95,22 @@ const TbmTransferModel = {
result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`; result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`;
} }
callback(null, result); return result;
} catch (err) { } catch (err) {
if (conn) {
try { await conn.rollback(); } catch (e) {} try { await conn.rollback(); } catch (e) {}
throw err;
} finally {
conn.release(); conn.release();
} }
callback(err);
}
}, },
/** /**
* 이동 취소 (원복) * 이동 취소 (원복)
*/ */
cancelTransfer: async (transferId, callback) => { async cancelTransfer(transferId) {
let conn;
try {
const db = await getDb(); const db = await getDb();
conn = await db.getConnection(); const conn = await db.getConnection();
try {
await conn.beginTransaction(); await conn.beginTransaction();
// 1. 이동 로그 조회 // 1. 이동 로그 조회
@@ -129,8 +121,7 @@ const TbmTransferModel = {
if (transfers.length === 0) { if (transfers.length === 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '이동 기록을 찾을 수 없습니다.' };
return callback(null, { success: false, message: '이동 기록을 찾을 수 없습니다.' });
} }
const t = transfers[0]; const t = transfers[0];
@@ -146,7 +137,6 @@ const TbmTransferModel = {
const newDestHours = destHours - parseFloat(t.hours); const newDestHours = destHours - parseFloat(t.hours);
if (newDestHours <= 0) { if (newDestHours <= 0) {
// 삭제
await conn.query( await conn.query(
'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', 'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?',
[t.dest_session_id, t.worker_id] [t.dest_session_id, t.worker_id]
@@ -168,7 +158,6 @@ const TbmTransferModel = {
if (sourceRows.length > 0) { if (sourceRows.length > 0) {
const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours); const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
const restoredHours = sourceHours + parseFloat(t.hours); const restoredHours = sourceHours + parseFloat(t.hours);
// 8이면 NULL로 복원 (종일)
await conn.query( await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
[restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.worker_id] [restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.worker_id]
@@ -179,23 +168,19 @@ const TbmTransferModel = {
await conn.query('DELETE FROM tbm_transfers WHERE transfer_id = ?', [transferId]); await conn.query('DELETE FROM tbm_transfers WHERE transfer_id = ?', [transferId]);
await conn.commit(); await conn.commit();
conn.release(); return { success: true };
callback(null, { success: true });
} catch (err) { } catch (err) {
if (conn) {
try { await conn.rollback(); } catch (e) {} try { await conn.rollback(); } catch (e) {}
throw err;
} finally {
conn.release(); conn.release();
} }
callback(err);
}
}, },
/** /**
* 당일 이동 내역 조회 * 당일 이동 내역 조회
*/ */
getTransfersByDate: async (date, callback) => { async getTransfersByDate(date) {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
SELECT SELECT
@@ -216,17 +201,13 @@ const TbmTransferModel = {
ORDER BY t.created_at DESC ORDER BY t.created_at DESC
`; `;
const [rows] = await db.query(sql, [date]); const [rows] = await db.query(sql, [date]);
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}, },
/** /**
* 당일 전 작업자 배정 현황 조회 * 당일 전 작업자 배정 현황 조회
*/ */
getWorkerAssignmentsByDate: async (date, callback) => { async getWorkerAssignmentsByDate(date) {
try {
const db = await getDb(); const db = await getDb();
// 1. 해당 날짜의 모든 배정 가져오기 // 1. 해당 날짜의 모든 배정 가져오기
@@ -279,15 +260,11 @@ const TbmTransferModel = {
} }
}); });
// available 판단
Object.values(workerMap).forEach(w => { Object.values(workerMap).forEach(w => {
w.available = w.total_hours < 8; w.available = w.total_hours < 8;
}); });
callback(null, Object.values(workerMap)); return Object.values(workerMap);
} catch (err) {
callback(err);
}
} }
}; };

View File

@@ -9,10 +9,9 @@ const vacationBalanceModel = {
/** /**
* 특정 작업자의 모든 휴가 잔액 조회 (특정 연도) * 특정 작업자의 모든 휴가 잔액 조회 (특정 연도)
*/ */
async getByWorkerAndYear(workerId, year, callback) { async getByWorkerAndYear(workerId, year) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT SELECT
vbd.*, vbd.*,
vt.type_name, vt.type_name,
@@ -23,21 +22,16 @@ const vacationBalanceModel = {
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
WHERE vbd.worker_id = ? AND vbd.year = ? WHERE vbd.worker_id = ? AND vbd.year = ?
ORDER BY vt.priority ASC, vt.type_name ASC ORDER BY vt.priority ASC, vt.type_name ASC
`; `, [workerId, year]);
const [rows] = await db.query(query, [workerId, year]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 특정 작업자의 특정 휴가 유형 잔액 조회 * 특정 작업자의 특정 휴가 유형 잔액 조회
*/ */
async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) { async getByWorkerTypeYear(workerId, vacationTypeId, year) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT SELECT
vbd.*, vbd.*,
vt.type_name, vt.type_name,
@@ -47,22 +41,16 @@ const vacationBalanceModel = {
WHERE vbd.worker_id = ? WHERE vbd.worker_id = ?
AND vbd.vacation_type_id = ? AND vbd.vacation_type_id = ?
AND vbd.year = ? AND vbd.year = ?
`; `, [workerId, vacationTypeId, year]);
const [rows] = await db.query(query, [workerId, vacationTypeId, year]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 모든 작업자의 휴가 잔액 조회 (특정 연도) * 모든 작업자의 휴가 잔액 조회 (특정 연도)
* - 연간 연차 현황 차트용
*/ */
async getAllByYear(year, callback) { async getAllByYear(year) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT SELECT
vbd.*, vbd.*,
w.worker_name, w.worker_name,
@@ -76,108 +64,75 @@ const vacationBalanceModel = {
WHERE vbd.year = ? WHERE vbd.year = ?
AND w.employment_status = 'employed' AND w.employment_status = 'employed'
ORDER BY w.worker_name ASC, vt.priority ASC ORDER BY w.worker_name ASC, vt.priority ASC
`; `, [year]);
const [rows] = await db.query(query, [year]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 생성 * 휴가 잔액 생성
*/ */
async create(balanceData, callback) { async create(balanceData) {
try {
const db = await getDb(); const db = await getDb();
const query = `INSERT INTO vacation_balance_details SET ?`; const [result] = await db.query(`INSERT INTO vacation_balance_details SET ?`, balanceData);
const [rows] = await db.query(query, balanceData); return result;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 수정 * 휴가 잔액 수정
*/ */
async update(id, updateData, callback) { async update(id, updateData) {
try {
const db = await getDb(); const db = await getDb();
const query = `UPDATE vacation_balance_details SET ? WHERE id = ?`; const [result] = await db.query(`UPDATE vacation_balance_details SET ? WHERE id = ?`, [updateData, id]);
const [rows] = await db.query(query, [updateData, id]); return result;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 삭제 * 휴가 잔액 삭제
*/ */
async delete(id, callback) { async delete(id) {
try {
const db = await getDb(); const db = await getDb();
const query = `DELETE FROM vacation_balance_details WHERE id = ?`; const [result] = await db.query(`DELETE FROM vacation_balance_details WHERE id = ?`, [id]);
const [rows] = await db.query(query, [id]); return result;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자의 휴가 사용 일수 업데이트 (차감) * 작업자의 휴가 사용 일수 업데이트 (차감)
* - 휴가 신청 승인 시 호출
*/ */
async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) { async deductDays(workerId, vacationTypeId, year, daysToDeduct) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [result] = await db.query(`
UPDATE vacation_balance_details UPDATE vacation_balance_details
SET used_days = used_days + ?, SET used_days = used_days + ?,
updated_at = NOW() updated_at = NOW()
WHERE worker_id = ? WHERE worker_id = ?
AND vacation_type_id = ? AND vacation_type_id = ?
AND year = ? AND year = ?
`; `, [daysToDeduct, workerId, vacationTypeId, year]);
const [rows] = await db.query(query, [daysToDeduct, workerId, vacationTypeId, year]); return result;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자의 휴가 사용 일수 복구 (취소) * 작업자의 휴가 사용 일수 복구 (취소)
* - 휴가 신청 취소/거부 시 호출
*/ */
async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) { async restoreDays(workerId, vacationTypeId, year, daysToRestore) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [result] = await db.query(`
UPDATE vacation_balance_details UPDATE vacation_balance_details
SET used_days = GREATEST(0, used_days - ?), SET used_days = GREATEST(0, used_days - ?),
updated_at = NOW() updated_at = NOW()
WHERE worker_id = ? WHERE worker_id = ?
AND vacation_type_id = ? AND vacation_type_id = ?
AND year = ? AND year = ?
`; `, [daysToRestore, workerId, vacationTypeId, year]);
const [rows] = await db.query(query, [daysToRestore, workerId, vacationTypeId, year]); return result;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 특정 작업자의 사용 가능한 휴가 일수 확인 * 특정 작업자의 사용 가능한 휴가 일수 확인
* - 우선순위가 높은 순서대로 차감 가능 여부 확인
*/ */
async getAvailableVacationDays(workerId, year, callback) { async getAvailableVacationDays(workerId, year) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT SELECT
vbd.id, vbd.id,
vbd.vacation_type_id, vbd.vacation_type_id,
@@ -193,26 +148,19 @@ const vacationBalanceModel = {
AND vbd.year = ? AND vbd.year = ?
AND vbd.remaining_days > 0 AND vbd.remaining_days > 0
ORDER BY vt.priority ASC ORDER BY vt.priority ASC
`; `, [workerId, year]);
const [rows] = await db.query(query, [workerId, year]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자별 휴가 잔액 일괄 생성 (연도별) * 작업자별 휴가 잔액 일괄 생성 (연도별)
* - 매년 초 또는 입사 시 사용
*/ */
async bulkCreate(balances, callback) { async bulkCreate(balances) {
try {
const db = await getDb();
if (!balances || balances.length === 0) { if (!balances || balances.length === 0) {
return callback(new Error('생성할 휴가 잔액 데이터가 없습니다')); throw new Error('생성할 휴가 잔액 데이터가 없습니다');
} }
const db = await getDb();
const query = `INSERT INTO vacation_balance_details const query = `INSERT INTO vacation_balance_details
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
VALUES ?`; VALUES ?`;
@@ -227,33 +175,24 @@ const vacationBalanceModel = {
b.created_by b.created_by
]); ]);
const [rows] = await db.query(query, [values]); const [result] = await db.query(query, [values]);
callback(null, rows); return result;
} catch (error) {
callback(error);
}
}, },
/** /**
* 근속년수 기반 연차 일수 계산 (한국 근로기준법) * 근속년수 기반 연차 일수 계산 (한국 근로기준법)
* @param {Date} hireDate - 입사일
* @param {number} targetYear - 대상 연도
* @returns {number} - 부여받을 연차 일수
*/ */
calculateAnnualLeaveDays(hireDate, targetYear) { calculateAnnualLeaveDays(hireDate, targetYear) {
const hire = new Date(hireDate); const hire = new Date(hireDate);
const targetDate = new Date(targetYear, 0, 1); const targetDate = new Date(targetYear, 0, 1);
// 근속 월수 계산
const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12 const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12
+ (targetDate.getMonth() - hire.getMonth()); + (targetDate.getMonth() - hire.getMonth());
// 1년 미만: 월 1일
if (monthsDiff < 12) { if (monthsDiff < 12) {
return Math.floor(monthsDiff); return Math.floor(monthsDiff);
} }
// 1년 이상: 15일 기본 + 2년마다 1일 추가 (최대 25일)
const yearsWorked = Math.floor(monthsDiff / 12); const yearsWorked = Math.floor(monthsDiff / 12);
const additionalDays = Math.floor((yearsWorked - 1) / 2); const additionalDays = Math.floor((yearsWorked - 1) / 2);
@@ -261,17 +200,11 @@ const vacationBalanceModel = {
}, },
/** /**
* 휴가 사용 시 우선순위에 따라 잔액에서 차감 (Promise 버전) * 휴가 사용 시 우선순위에 따라 잔액에서 차감
* - 일일 근태 기록 저장 시 호출
* @param {number} workerId - 작업자 ID
* @param {number} year - 연도
* @param {number} daysToDeduct - 차감할 일수 (1, 0.5, 0.25)
* @returns {Promise<Object>} - 차감 결과
*/ */
async deductByPriority(workerId, year, daysToDeduct) { async deductByPriority(workerId, year, daysToDeduct) {
const db = await getDb(); const db = await getDb();
// 우선순위순으로 잔여 일수가 있는 잔액 조회
const [balances] = await db.query(` const [balances] = await db.query(`
SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days, SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days,
(vbd.total_days - vbd.used_days) as remaining_days, (vbd.total_days - vbd.used_days) as remaining_days,
@@ -284,7 +217,6 @@ const vacationBalanceModel = {
`, [workerId, year]); `, [workerId, year]);
if (balances.length === 0) { if (balances.length === 0) {
// 잔액이 없어도 일단 기록은 저장 (경고만)
console.warn(`[VacationBalance] 작업자 ${workerId}${year}년 휴가 잔액이 없습니다`); console.warn(`[VacationBalance] 작업자 ${workerId}${year}년 휴가 잔액이 없습니다`);
return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 }; return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 };
} }
@@ -321,16 +253,11 @@ const vacationBalanceModel = {
}, },
/** /**
* 휴가 취소 시 우선순위 역순으로 복구 (Promise 버전) * 휴가 취소 시 우선순위 역순으로 복구
* @param {number} workerId - 작업자 ID
* @param {number} year - 연도
* @param {number} daysToRestore - 복구할 일수
* @returns {Promise<Object>} - 복구 결과
*/ */
async restoreByPriority(workerId, year, daysToRestore) { async restoreByPriority(workerId, year, daysToRestore) {
const db = await getDb(); const db = await getDb();
// 우선순위 역순으로 사용 일수가 있는 잔액 조회 (나중에 차감된 것부터 복구)
const [balances] = await db.query(` const [balances] = await db.query(`
SELECT vbd.id, vbd.vacation_type_id, vbd.used_days, SELECT vbd.id, vbd.vacation_type_id, vbd.used_days,
vt.type_code, vt.type_name, vt.priority vt.type_code, vt.type_name, vt.priority
@@ -375,10 +302,9 @@ const vacationBalanceModel = {
/** /**
* 특정 ID로 휴가 잔액 조회 * 특정 ID로 휴가 잔액 조회
*/ */
async getById(id, callback) { async getById(id) {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT SELECT
vbd.*, vbd.*,
w.worker_name, w.worker_name,
@@ -388,12 +314,8 @@ const vacationBalanceModel = {
INNER JOIN workers w ON vbd.worker_id = w.worker_id INNER JOIN workers w ON vbd.worker_id = w.worker_id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
WHERE vbd.id = ? WHERE vbd.id = ?
`; `, [id]);
const [rows] = await db.query(query, [id]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
} }
}; };

View File

@@ -9,123 +9,97 @@ const vacationTypeModel = {
/** /**
* 모든 활성 휴가 유형 조회 (우선순위 순서대로) * 모든 활성 휴가 유형 조회 (우선순위 순서대로)
*/ */
async getAll(callback) { async getAll() {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT * SELECT *
FROM vacation_types FROM vacation_types
WHERE is_active = 1 WHERE is_active = 1
ORDER BY priority ASC, id ASC ORDER BY priority ASC, id ASC
`; `);
const [rows] = await db.query(query); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 시스템 기본 휴가 유형만 조회 * 시스템 기본 휴가 유형만 조회
*/ */
async getSystemTypes(callback) { async getSystemTypes() {
try {
const db = await getDb(); const db = await getDb();
const query = ` const [rows] = await db.query(`
SELECT * SELECT *
FROM vacation_types FROM vacation_types
WHERE is_system = 1 AND is_active = 1 WHERE is_system = 1 AND is_active = 1
ORDER BY priority ASC ORDER BY priority ASC
`; `);
const [rows] = await db.query(query); return rows;
callback(null, rows); },
} catch (error) {
callback(error); /**
} * 특별 휴가 유형만 조회
*/
async getSpecialTypes() {
const db = await getDb();
const [rows] = await db.query(`
SELECT *
FROM vacation_types
WHERE is_special = 1 AND is_active = 1
ORDER BY priority ASC
`);
return rows;
}, },
/** /**
* 특정 ID로 휴가 유형 조회 * 특정 ID로 휴가 유형 조회
*/ */
async getById(id, callback) { async getById(id) {
try {
const db = await getDb(); const db = await getDb();
const query = `SELECT * FROM vacation_types WHERE id = ?`; const [rows] = await db.query(`SELECT * FROM vacation_types WHERE id = ?`, [id]);
const [rows] = await db.query(query, [id]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 코드로 조회 * 휴가 유형 코드로 조회
*/ */
async getByCode(code, callback) { async getByCode(code) {
try {
const db = await getDb(); const db = await getDb();
const query = `SELECT * FROM vacation_types WHERE type_code = ?`; const [rows] = await db.query(`SELECT * FROM vacation_types WHERE type_code = ?`, [code]);
const [rows] = await db.query(query, [code]); return rows;
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 생성 * 휴가 유형 생성
*/ */
async create(typeData, callback) { async create(typeData) {
try {
const db = await getDb(); const db = await getDb();
const query = `INSERT INTO vacation_types SET ?`; const [result] = await db.query(`INSERT INTO vacation_types SET ?`, typeData);
const [result] = await db.query(query, typeData); return result;
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 수정 * 휴가 유형 수정
*/ */
async update(id, updateData, callback) { async update(id, updateData) {
try {
const db = await getDb(); const db = await getDb();
const query = `UPDATE vacation_types SET ? WHERE id = ?`; const [result] = await db.query(`UPDATE vacation_types SET ? WHERE id = ?`, [updateData, id]);
const [result] = await db.query(query, [updateData, id]); return result;
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 삭제 (논리적 삭제 - is_active = 0) * 휴가 유형 삭제 (논리적 삭제 - is_active = 0)
*/ */
async delete(id, callback) { async delete(id) {
try {
const db = await getDb(); const db = await getDb();
const query = `UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`; const [result] = await db.query(`UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`, [id]);
const [result] = await db.query(query, [id]); return result;
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 우선순위 업데이트 * 우선순위 업데이트
*/ */
async updatePriority(id, priority, callback) { async updatePriority(id, priority) {
try {
const db = await getDb(); const db = await getDb();
const query = `UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`; const [result] = await db.query(`UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`, [priority, id]);
const [result] = await db.query(query, [priority, id]); return result;
callback(null, result);
} catch (error) {
callback(error);
}
} }
}; };

View File

@@ -2,11 +2,7 @@ const { getDb } = require('../dbPool');
// ==================== 출입 신청 관리 ==================== // ==================== 출입 신청 관리 ====================
/** const createVisitRequest = async (requestData) => {
* 출입 신청 생성
*/
const createVisitRequest = async (requestData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
requester_id, requester_id,
@@ -29,17 +25,10 @@ const createVisitRequest = async (requestData, callback) => {
visit_date, visit_time, purpose_id, notes] visit_date, visit_time, purpose_id, notes]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllVisitRequests = async (filters = {}) => {
* 출입 신청 목록 조회 (필터 옵션 포함)
*/
const getAllVisitRequests = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let query = ` let query = `
SELECT SELECT
@@ -63,27 +52,22 @@ const getAllVisitRequests = async (filters = {}, callback) => {
const params = []; const params = [];
// 필터 적용
if (filters.status) { if (filters.status) {
query += ` AND vr.status = ?`; query += ` AND vr.status = ?`;
params.push(filters.status); params.push(filters.status);
} }
if (filters.visit_date) { if (filters.visit_date) {
query += ` AND vr.visit_date = ?`; query += ` AND vr.visit_date = ?`;
params.push(filters.visit_date); params.push(filters.visit_date);
} }
if (filters.start_date && filters.end_date) { if (filters.start_date && filters.end_date) {
query += ` AND vr.visit_date BETWEEN ? AND ?`; query += ` AND vr.visit_date BETWEEN ? AND ?`;
params.push(filters.start_date, filters.end_date); params.push(filters.start_date, filters.end_date);
} }
if (filters.requester_id) { if (filters.requester_id) {
query += ` AND vr.requester_id = ?`; query += ` AND vr.requester_id = ?`;
params.push(filters.requester_id); params.push(filters.requester_id);
} }
if (filters.category_id) { if (filters.category_id) {
query += ` AND vr.category_id = ?`; query += ` AND vr.category_id = ?`;
params.push(filters.category_id); params.push(filters.category_id);
@@ -92,17 +76,10 @@ const getAllVisitRequests = async (filters = {}, callback) => {
query += ` ORDER BY vr.visit_date DESC, vr.visit_time DESC, vr.created_at DESC`; query += ` ORDER BY vr.visit_date DESC, vr.visit_time DESC, vr.created_at DESC`;
const [rows] = await db.query(query, params); const [rows] = await db.query(query, params);
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getVisitRequestById = async (requestId) => {
* 출입 신청 상세 조회
*/
const getVisitRequestById = async (requestId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT `SELECT
@@ -124,28 +101,14 @@ const getVisitRequestById = async (requestId, callback) => {
WHERE vr.request_id = ?`, WHERE vr.request_id = ?`,
[requestId] [requestId]
); );
return rows[0];
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitRequest = async (requestId, requestData) => {
* 출입 신청 수정
*/
const updateVisitRequest = async (requestId, requestData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
visitor_company, visitor_company, visitor_count, category_id, workplace_id,
visitor_count, visit_date, visit_time, purpose_id, notes
category_id,
workplace_id,
visit_date,
visit_time,
purpose_id,
notes
} = requestData; } = requestData;
const [result] = await db.query( const [result] = await db.query(
@@ -156,34 +119,19 @@ const updateVisitRequest = async (requestId, requestData, callback) => {
[visitor_company, visitor_count, category_id, workplace_id, [visitor_company, visitor_count, category_id, workplace_id,
visit_date, visit_time, purpose_id, notes, requestId] visit_date, visit_time, purpose_id, notes, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const deleteVisitRequest = async (requestId) => {
* 출입 신청 삭제
*/
const deleteVisitRequest = async (requestId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM workplace_visit_requests WHERE request_id = ?`, `DELETE FROM workplace_visit_requests WHERE request_id = ?`,
[requestId] [requestId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const approveVisitRequest = async (requestId, approvedBy) => {
* 출입 신청 승인
*/
const approveVisitRequest = async (requestId, approvedBy, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_visit_requests `UPDATE workplace_visit_requests
@@ -191,18 +139,10 @@ const approveVisitRequest = async (requestId, approvedBy, callback) => {
WHERE request_id = ?`, WHERE request_id = ?`,
[approvedBy, requestId] [approvedBy, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const rejectVisitRequest = async (requestId, rejectionData) => {
* 출입 신청 반려
*/
const rejectVisitRequest = async (requestId, rejectionData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { approved_by, rejection_reason } = rejectionData; const { approved_by, rejection_reason } = rejectionData;
@@ -213,18 +153,10 @@ const rejectVisitRequest = async (requestId, rejectionData, callback) => {
WHERE request_id = ?`, WHERE request_id = ?`,
[approved_by, rejection_reason, requestId] [approved_by, rejection_reason, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitRequestStatus = async (requestId, status) => {
* 출입 신청 상태 변경
*/
const updateVisitRequestStatus = async (requestId, status, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_visit_requests `UPDATE workplace_visit_requests
@@ -232,37 +164,22 @@ const updateVisitRequestStatus = async (requestId, status, callback) => {
WHERE request_id = ?`, WHERE request_id = ?`,
[status, requestId] [status, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
// ==================== 방문 목적 관리 ==================== // ==================== 방문 목적 관리 ====================
/** const getAllVisitPurposes = async () => {
* 모든 방문 목적 조회
*/
const getAllVisitPurposes = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT purpose_id, purpose_name, display_order, is_active, created_at `SELECT purpose_id, purpose_name, display_order, is_active, created_at
FROM visit_purpose_types FROM visit_purpose_types
ORDER BY display_order ASC, purpose_id ASC` ORDER BY display_order ASC, purpose_id ASC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getActiveVisitPurposes = async () => {
* 활성 방문 목적만 조회
*/
const getActiveVisitPurposes = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT purpose_id, purpose_name, display_order, is_active, created_at `SELECT purpose_id, purpose_name, display_order, is_active, created_at
@@ -270,17 +187,10 @@ const getActiveVisitPurposes = async (callback) => {
WHERE is_active = TRUE WHERE is_active = TRUE
ORDER BY display_order ASC, purpose_id ASC` ORDER BY display_order ASC, purpose_id ASC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const createVisitPurpose = async (purposeData) => {
* 방문 목적 추가
*/
const createVisitPurpose = async (purposeData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { purpose_name, display_order = 0, is_active = true } = purposeData; const { purpose_name, display_order = 0, is_active = true } = purposeData;
@@ -289,18 +199,10 @@ const createVisitPurpose = async (purposeData, callback) => {
VALUES (?, ?, ?)`, VALUES (?, ?, ?)`,
[purpose_name, display_order, is_active] [purpose_name, display_order, is_active]
); );
return result.insertId;
callback(null, result.insertId);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitPurpose = async (purposeId, purposeData) => {
* 방문 목적 수정
*/
const updateVisitPurpose = async (purposeId, purposeData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { purpose_name, display_order, is_active } = purposeData; const { purpose_name, display_order, is_active } = purposeData;
@@ -310,44 +212,25 @@ const updateVisitPurpose = async (purposeId, purposeData, callback) => {
WHERE purpose_id = ?`, WHERE purpose_id = ?`,
[purpose_name, display_order, is_active, purposeId] [purpose_name, display_order, is_active, purposeId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const deleteVisitPurpose = async (purposeId) => {
* 방문 목적 삭제
*/
const deleteVisitPurpose = async (purposeId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`, `DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
[purposeId] [purposeId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
// ==================== 안전교육 기록 관리 ==================== // ==================== 안전교육 기록 관리 ====================
/** const createTrainingRecord = async (trainingData) => {
* 안전교육 기록 생성
*/
const createTrainingRecord = async (trainingData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
request_id, request_id, trainer_id, training_date,
trainer_id, training_start_time, training_end_time = null, training_topics = null
training_date,
training_start_time,
training_end_time = null,
training_topics = null
} = trainingData; } = trainingData;
const [result] = await db.query( const [result] = await db.query(
@@ -356,18 +239,10 @@ const createTrainingRecord = async (trainingData, callback) => {
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics] [request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
); );
return result.insertId;
callback(null, result.insertId);
} catch (err) {
callback(err);
}
}; };
/** const getTrainingRecordByRequestId = async (requestId) => {
* 특정 출입 신청의 안전교육 기록 조회
*/
const getTrainingRecordByRequestId = async (requestId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT `SELECT
@@ -380,25 +255,12 @@ const getTrainingRecordByRequestId = async (requestId, callback) => {
WHERE str.request_id = ?`, WHERE str.request_id = ?`,
[requestId] [requestId]
); );
return rows[0];
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateTrainingRecord = async (trainingId, trainingData) => {
* 안전교육 기록 수정
*/
const updateTrainingRecord = async (trainingId, trainingData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const { training_date, training_start_time, training_end_time, training_topics } = trainingData;
training_date,
training_start_time,
training_end_time,
training_topics
} = trainingData;
const [result] = await db.query( const [result] = await db.query(
`UPDATE safety_training_records `UPDATE safety_training_records
@@ -407,18 +269,10 @@ const updateTrainingRecord = async (trainingId, trainingData, callback) => {
WHERE training_id = ?`, WHERE training_id = ?`,
[training_date, training_start_time, training_end_time, training_topics, trainingId] [training_date, training_start_time, training_end_time, training_topics, trainingId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const completeTraining = async (trainingId, signatureData) => {
* 안전교육 완료 (서명 포함)
*/
const completeTraining = async (trainingId, signatureData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`UPDATE safety_training_records `UPDATE safety_training_records
@@ -426,18 +280,10 @@ const completeTraining = async (trainingId, signatureData, callback) => {
WHERE training_id = ?`, WHERE training_id = ?`,
[signatureData, trainingId] [signatureData, trainingId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const getTrainingRecords = async (filters = {}) => {
* 안전교육 목록 조회 (날짜별 필터)
*/
const getTrainingRecords = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let query = ` let query = `
SELECT SELECT
@@ -458,12 +304,10 @@ const getTrainingRecords = async (filters = {}, callback) => {
query += ` AND str.training_date = ?`; query += ` AND str.training_date = ?`;
params.push(filters.training_date); params.push(filters.training_date);
} }
if (filters.start_date && filters.end_date) { if (filters.start_date && filters.end_date) {
query += ` AND str.training_date BETWEEN ? AND ?`; query += ` AND str.training_date BETWEEN ? AND ?`;
params.push(filters.start_date, filters.end_date); params.push(filters.start_date, filters.end_date);
} }
if (filters.trainer_id) { if (filters.trainer_id) {
query += ` AND str.trainer_id = ?`; query += ` AND str.trainer_id = ?`;
params.push(filters.trainer_id); params.push(filters.trainer_id);
@@ -472,14 +316,10 @@ const getTrainingRecords = async (filters = {}, callback) => {
query += ` ORDER BY str.training_date DESC, str.training_start_time DESC`; query += ` ORDER BY str.training_date DESC, str.training_start_time DESC`;
const [rows] = await db.query(query, params); const [rows] = await db.query(query, params);
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
module.exports = { module.exports = {
// 출입 신청
createVisitRequest, createVisitRequest,
getAllVisitRequests, getAllVisitRequests,
getVisitRequestById, getVisitRequestById,
@@ -488,15 +328,11 @@ module.exports = {
approveVisitRequest, approveVisitRequest,
rejectVisitRequest, rejectVisitRequest,
updateVisitRequestStatus, updateVisitRequestStatus,
// 방문 목적
getAllVisitPurposes, getAllVisitPurposes,
getActiveVisitPurposes, getActiveVisitPurposes,
createVisitPurpose, createVisitPurpose,
updateVisitPurpose, updateVisitPurpose,
deleteVisitPurpose, deleteVisitPurpose,
// 안전교육
createTrainingRecord, createTrainingRecord,
getTrainingRecordByRequestId, getTrainingRecordByRequestId,
updateTrainingRecord, updateTrainingRecord,

View File

@@ -7,28 +7,17 @@ const { getDb } = require('../dbPool');
// ==================== 신고 카테고리 관리 ==================== // ==================== 신고 카테고리 관리 ====================
/** const getAllCategories = async () => {
* 모든 신고 카테고리 조회
*/
const getAllCategories = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT category_id, category_type, category_name, description, display_order, is_active, created_at `SELECT category_id, category_type, category_name, description, display_order, is_active, created_at
FROM issue_report_categories FROM issue_report_categories
ORDER BY category_type, display_order, category_id` ORDER BY category_type, display_order, category_id`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getCategoriesByType = async (categoryType) => {
* 타입별 활성 카테고리 조회 (nonconformity/safety)
*/
const getCategoriesByType = async (categoryType, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT category_id, category_type, category_name, description, display_order `SELECT category_id, category_type, category_name, description, display_order
@@ -37,17 +26,10 @@ const getCategoriesByType = async (categoryType, callback) => {
ORDER BY display_order, category_id`, ORDER BY display_order, category_id`,
[categoryType] [categoryType]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const createCategory = async (categoryData) => {
* 카테고리 생성
*/
const createCategory = async (categoryData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { category_type, category_name, description = null, display_order = 0 } = categoryData; const { category_type, category_name, description = null, display_order = 0 } = categoryData;
@@ -57,17 +39,10 @@ const createCategory = async (categoryData, callback) => {
[category_type, category_name, description, display_order] [category_type, category_name, description, display_order]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const updateCategory = async (categoryId, categoryData) => {
* 카테고리 수정
*/
const updateCategory = async (categoryId, categoryData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { category_name, description, display_order, is_active } = categoryData; const { category_name, description, display_order, is_active } = categoryData;
@@ -78,35 +53,21 @@ const updateCategory = async (categoryId, categoryData, callback) => {
[category_name, description, display_order, is_active, categoryId] [category_name, description, display_order, is_active, categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteCategory = async (categoryId) => {
* 카테고리 삭제
*/
const deleteCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM issue_report_categories WHERE category_id = ?`, `DELETE FROM issue_report_categories WHERE category_id = ?`,
[categoryId] [categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
// ==================== 사전 정의 신고 항목 관리 ==================== // ==================== 사전 정의 신고 항목 관리 ====================
/** const getItemsByCategory = async (categoryId) => {
* 카테고리별 활성 항목 조회
*/
const getItemsByCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT item_id, category_id, item_name, description, severity, display_order `SELECT item_id, category_id, item_name, description, severity, display_order
@@ -115,17 +76,10 @@ const getItemsByCategory = async (categoryId, callback) => {
ORDER BY display_order, item_id`, ORDER BY display_order, item_id`,
[categoryId] [categoryId]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getAllItems = async () => {
* 모든 항목 조회 (관리용)
*/
const getAllItems = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT iri.item_id, iri.category_id, iri.item_name, iri.description, `SELECT iri.item_id, iri.category_id, iri.item_name, iri.description,
@@ -135,17 +89,10 @@ const getAllItems = async (callback) => {
INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id
ORDER BY irc.category_type, irc.display_order, iri.display_order, iri.item_id` ORDER BY irc.category_type, irc.display_order, iri.display_order, iri.item_id`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const createItem = async (itemData) => {
* 항목 생성
*/
const createItem = async (itemData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { category_id, item_name, description = null, severity = 'medium', display_order = 0 } = itemData; const { category_id, item_name, description = null, severity = 'medium', display_order = 0 } = itemData;
@@ -155,17 +102,10 @@ const createItem = async (itemData, callback) => {
[category_id, item_name, description, severity, display_order] [category_id, item_name, description, severity, display_order]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const updateItem = async (itemId, itemData) => {
* 항목 수정
*/
const updateItem = async (itemId, itemData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { item_name, description, severity, display_order, is_active } = itemData; const { item_name, description, severity, display_order, is_active } = itemData;
@@ -176,26 +116,16 @@ const updateItem = async (itemId, itemData, callback) => {
[item_name, description, severity, display_order, is_active, itemId] [item_name, description, severity, display_order, is_active, itemId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteItem = async (itemId) => {
* 항목 삭제
*/
const deleteItem = async (itemId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM issue_report_items WHERE item_id = ?`, `DELETE FROM issue_report_items WHERE item_id = ?`,
[itemId] [itemId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
// ==================== 문제 신고 관리 ==================== // ==================== 문제 신고 관리 ====================
@@ -203,11 +133,7 @@ const deleteItem = async (itemId, callback) => {
// 한국 시간 유틸리티 import // 한국 시간 유틸리티 import
const { getKoreaDatetime } = require('../utils/dateUtils'); const { getKoreaDatetime } = require('../utils/dateUtils');
/** const createReport = async (reportData) => {
* 신고 생성
*/
const createReport = async (reportData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
reporter_id, reporter_id,
@@ -226,7 +152,6 @@ const createReport = async (reportData, callback) => {
photo_path5 = null photo_path5 = null
} = reportData; } = reportData;
// 한국 시간 기준으로 신고 일시 설정
const reportDate = getKoreaDatetime(); const reportDate = getKoreaDatetime();
const [result] = await db.query( const [result] = await db.query(
@@ -240,24 +165,16 @@ const createReport = async (reportData, callback) => {
additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5] additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5]
); );
// 상태 변경 로그 기록
await db.query( await db.query(
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
VALUES (?, NULL, 'reported', ?)`, VALUES (?, NULL, 'reported', ?)`,
[result.insertId, reporter_id] [result.insertId, reporter_id]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllReports = async (filters = {}) => {
* 신고 목록 조회 (필터 옵션 포함)
*/
const getAllReports = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let query = ` let query = `
SELECT SELECT
@@ -287,7 +204,6 @@ const getAllReports = async (filters = {}, callback) => {
const params = []; const params = [];
// 필터 적용
if (filters.status) { if (filters.status) {
query += ` AND wir.status = ?`; query += ` AND wir.status = ?`;
params.push(filters.status); params.push(filters.status);
@@ -336,7 +252,6 @@ const getAllReports = async (filters = {}, callback) => {
query += ` ORDER BY wir.report_date DESC, wir.report_id DESC`; query += ` ORDER BY wir.report_date DESC, wir.report_id DESC`;
// 페이지네이션
if (filters.limit) { if (filters.limit) {
query += ` LIMIT ?`; query += ` LIMIT ?`;
params.push(parseInt(filters.limit)); params.push(parseInt(filters.limit));
@@ -348,17 +263,10 @@ const getAllReports = async (filters = {}, callback) => {
} }
const [rows] = await db.query(query, params); const [rows] = await db.query(query, params);
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getReportById = async (reportId) => {
* 신고 상세 조회
*/
const getReportById = async (reportId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT `SELECT
@@ -393,32 +301,23 @@ const getReportById = async (reportId, callback) => {
[reportId] [reportId]
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** const updateReport = async (reportId, reportData, userId) => {
* 신고 수정
*/
const updateReport = async (reportId, reportData, userId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 기존 데이터 조회
const [existing] = await db.query( const [existing] = await db.query(
`SELECT * FROM work_issue_reports WHERE report_id = ?`, `SELECT * FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (existing.length === 0) { if (existing.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
const current = existing[0]; const current = existing[0];
// 수정 이력 생성
const modifications = []; const modifications = [];
const now = new Date().toISOString(); const now = new Date().toISOString();
@@ -434,7 +333,6 @@ const updateReport = async (reportId, reportData, userId, callback) => {
} }
} }
// 기존 이력과 병합
const existingHistory = current.modification_history ? JSON.parse(current.modification_history) : []; const existingHistory = current.modification_history ? JSON.parse(current.modification_history) : [];
const newHistory = [...existingHistory, ...modifications]; const newHistory = [...existingHistory, ...modifications];
@@ -474,20 +372,12 @@ const updateReport = async (reportId, reportData, userId, callback) => {
JSON.stringify(newHistory), reportId] JSON.stringify(newHistory), reportId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteReport = async (reportId) => {
* 신고 삭제
*/
const deleteReport = async (reportId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 먼저 사진 경로 조회 (삭제용)
const [photos] = await db.query( const [photos] = await db.query(
`SELECT photo_path1, photo_path2, photo_path3, photo_path4, photo_path5, `SELECT photo_path1, photo_path2, photo_path3, photo_path4, photo_path5,
resolution_photo_path1, resolution_photo_path2 resolution_photo_path1, resolution_photo_path2
@@ -500,34 +390,25 @@ const deleteReport = async (reportId, callback) => {
[reportId] [reportId]
); );
// 삭제할 사진 경로 반환 return { result, photos: photos[0] };
callback(null, { result, photos: photos[0] });
} catch (err) {
callback(err);
}
}; };
// ==================== 상태 관리 ==================== // ==================== 상태 관리 ====================
/** const receiveReport = async (reportId, userId) => {
* 신고 접수 (reported → received)
*/
const receiveReport = async (reportId, userId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 현재 상태 확인
const [current] = await db.query( const [current] = await db.query(
`SELECT status FROM work_issue_reports WHERE report_id = ?`, `SELECT status FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (current.length === 0) { if (current.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
if (current[0].status !== 'reported') { if (current[0].status !== 'reported') {
return callback(new Error('접수 대기 상태가 아닙니다.')); throw new Error('접수 대기 상태가 아닙니다.');
} }
const [result] = await db.query( const [result] = await db.query(
@@ -537,41 +418,31 @@ const receiveReport = async (reportId, userId, callback) => {
[reportId] [reportId]
); );
// 상태 변경 로그
await db.query( await db.query(
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
VALUES (?, 'reported', 'received', ?)`, VALUES (?, 'reported', 'received', ?)`,
[reportId, userId] [reportId, userId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const assignReport = async (reportId, assignData) => {
* 담당자 배정
*/
const assignReport = async (reportId, assignData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { assigned_department, assigned_user_id, assigned_by } = assignData; const { assigned_department, assigned_user_id, assigned_by } = assignData;
// 현재 상태 확인
const [current] = await db.query( const [current] = await db.query(
`SELECT status FROM work_issue_reports WHERE report_id = ?`, `SELECT status FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (current.length === 0) { if (current.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
// 접수 상태 이상이어야 배정 가능
const validStatuses = ['received', 'in_progress']; const validStatuses = ['received', 'in_progress'];
if (!validStatuses.includes(current[0].status)) { if (!validStatuses.includes(current[0].status)) {
return callback(new Error('접수된 상태에서만 담당자 배정이 가능합니다.')); throw new Error('접수된 상태에서만 담당자 배정이 가능합니다.');
} }
const [result] = await db.query( const [result] = await db.query(
@@ -582,31 +453,23 @@ const assignReport = async (reportId, assignData, callback) => {
[assigned_department, assigned_user_id, assigned_by, reportId] [assigned_department, assigned_user_id, assigned_by, reportId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const startProcessing = async (reportId, userId) => {
* 처리 시작 (received → in_progress)
*/
const startProcessing = async (reportId, userId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 현재 상태 확인
const [current] = await db.query( const [current] = await db.query(
`SELECT status FROM work_issue_reports WHERE report_id = ?`, `SELECT status FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (current.length === 0) { if (current.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
if (current[0].status !== 'received') { if (current[0].status !== 'received') {
return callback(new Error('접수된 상태에서만 처리를 시작할 수 있습니다.')); throw new Error('접수된 상태에서만 처리를 시작할 수 있습니다.');
} }
const [result] = await db.query( const [result] = await db.query(
@@ -616,39 +479,30 @@ const startProcessing = async (reportId, userId, callback) => {
[reportId] [reportId]
); );
// 상태 변경 로그
await db.query( await db.query(
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
VALUES (?, 'received', 'in_progress', ?)`, VALUES (?, 'received', 'in_progress', ?)`,
[reportId, userId] [reportId, userId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const completeReport = async (reportId, completionData) => {
* 처리 완료 (in_progress → completed)
*/
const completeReport = async (reportId, completionData, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by } = completionData; const { resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by } = completionData;
// 현재 상태 확인
const [current] = await db.query( const [current] = await db.query(
`SELECT status FROM work_issue_reports WHERE report_id = ?`, `SELECT status FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (current.length === 0) { if (current.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
if (current[0].status !== 'in_progress') { if (current[0].status !== 'in_progress') {
return callback(new Error('처리 중 상태에서만 완료할 수 있습니다.')); throw new Error('처리 중 상태에서만 완료할 수 있습니다.');
} }
const [result] = await db.query( const [result] = await db.query(
@@ -660,38 +514,29 @@ const completeReport = async (reportId, completionData, callback) => {
[resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by, reportId] [resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by, reportId]
); );
// 상태 변경 로그
await db.query( await db.query(
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by, change_reason) `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by, change_reason)
VALUES (?, 'in_progress', 'completed', ?, ?)`, VALUES (?, 'in_progress', 'completed', ?, ?)`,
[reportId, resolved_by, resolution_notes] [reportId, resolved_by, resolution_notes]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const closeReport = async (reportId, userId) => {
* 신고 종료 (completed → closed)
*/
const closeReport = async (reportId, userId, callback) => {
try {
const db = await getDb(); const db = await getDb();
// 현재 상태 확인
const [current] = await db.query( const [current] = await db.query(
`SELECT status FROM work_issue_reports WHERE report_id = ?`, `SELECT status FROM work_issue_reports WHERE report_id = ?`,
[reportId] [reportId]
); );
if (current.length === 0) { if (current.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.')); throw new Error('신고를 찾을 수 없습니다.');
} }
if (current[0].status !== 'completed') { if (current[0].status !== 'completed') {
return callback(new Error('완료된 상태에서만 종료할 수 있습니다.')); throw new Error('완료된 상태에서만 종료할 수 있습니다.');
} }
const [result] = await db.query( const [result] = await db.query(
@@ -701,24 +546,16 @@ const closeReport = async (reportId, userId, callback) => {
[reportId] [reportId]
); );
// 상태 변경 로그
await db.query( await db.query(
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
VALUES (?, 'completed', 'closed', ?)`, VALUES (?, 'completed', 'closed', ?)`,
[reportId, userId] [reportId, userId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const getStatusLogs = async (reportId) => {
* 상태 변경 이력 조회
*/
const getStatusLogs = async (reportId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT wisl.log_id, wisl.report_id, wisl.previous_status, wisl.new_status, `SELECT wisl.log_id, wisl.report_id, wisl.previous_status, wisl.new_status,
@@ -730,19 +567,12 @@ const getStatusLogs = async (reportId, callback) => {
ORDER BY wisl.changed_at ASC`, ORDER BY wisl.changed_at ASC`,
[reportId] [reportId]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
// ==================== 통계 ==================== // ==================== 통계 ====================
/** const getStatsSummary = async (filters = {}) => {
* 신고 통계 요약
*/
const getStatsSummary = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let whereClause = '1=1'; let whereClause = '1=1';
@@ -771,17 +601,10 @@ const getStatsSummary = async (filters = {}, callback) => {
params params
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** const getStatsByCategory = async (filters = {}) => {
* 카테고리별 통계
*/
const getStatsByCategory = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let whereClause = '1=1'; let whereClause = '1=1';
@@ -804,17 +627,10 @@ const getStatsByCategory = async (filters = {}, callback) => {
params params
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getStatsByWorkplace = async (filters = {}) => {
* 작업장별 통계
*/
const getStatsByWorkplace = async (filters = {}, callback) => {
try {
const db = await getDb(); const db = await getDb();
let whereClause = 'wir.workplace_id IS NOT NULL'; let whereClause = 'wir.workplace_id IS NOT NULL';
@@ -844,10 +660,7 @@ const getStatsByWorkplace = async (filters = {}, callback) => {
params params
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
module.exports = { module.exports = {

View File

@@ -3,7 +3,7 @@ const { getDb } = require('../dbPool');
/** /**
* 1. 여러 건 등록 (트랜잭션 사용) * 1. 여러 건 등록 (트랜잭션 사용)
*/ */
const createBatch = async (reports, callback) => { const createBatch = async (reports) => {
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -30,10 +30,9 @@ const createBatch = async (reports, callback) => {
} }
await conn.commit(); await conn.commit();
callback(null);
} catch (err) { } catch (err) {
await conn.rollback(); await conn.rollback();
callback(err); throw err;
} finally { } finally {
conn.release(); conn.release();
} }
@@ -42,8 +41,7 @@ const createBatch = async (reports, callback) => {
/** /**
* 2. 단일 등록 * 2. 단일 등록
*/ */
const create = async (report, callback) => { const create = async (report) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
date, worker_id, project_id, date, worker_id, project_id,
@@ -66,21 +64,17 @@ const create = async (report, callback) => {
] ]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** /**
* 3. 날짜별 조회 * 3. 날짜별 조회
*/ */
const getAllByDate = async (date, callback) => { const getAllByDate = async (date) => {
try {
const db = await getDb(); const db = await getDb();
const sql = ` const sql = `
SELECT SELECT
wr.worker_id, -- 이 줄을 추가했습니다 wr.worker_id,
wr.id, wr.id,
wr.\`date\`, wr.\`date\`,
w.worker_name, w.worker_name,
@@ -97,17 +91,13 @@ const create = async (report, callback) => {
ORDER BY w.worker_name ASC ORDER BY w.worker_name ASC
`; `;
const [rows] = await db.query(sql, [date]); const [rows] = await db.query(sql, [date]);
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** /**
* 4. 기간 조회 * 4. 기간 조회
*/ */
const getByRange = async (start, end, callback) => { const getByRange = async (start, end) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports
@@ -115,33 +105,25 @@ const getByRange = async (start, end, callback) => {
ORDER BY \`date\` ASC`, ORDER BY \`date\` ASC`,
[start, end] [start, end]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** /**
* 5. ID로 조회 * 5. ID로 조회
*/ */
const getById = async (id, callback) => { const getById = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`, `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`,
[id] [id]
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** /**
* 6. 수정 * 6. 수정
*/ */
const update = async (id, report, callback) => { const update = async (id, report) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
date, worker_id, project_id, date, worker_id, project_id,
@@ -172,45 +154,33 @@ const update = async (id, report, callback) => {
] ]
); );
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) {
callback(err);
}
}; };
/** /**
* 7. 삭제 * 7. 삭제
*/ */
const remove = async (id, callback) => { const remove = async (id) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM WorkReports WHERE id = ?`, `DELETE FROM WorkReports WHERE id = ?`,
[id] [id]
); );
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) {
callback(new Error(err.message || String(err)));
}
}; };
/** /**
* 8. 중복 확인 * 8. 중복 확인
*/ */
const existsByDateAndWorker = async (date, worker_id, callback) => { const existsByDateAndWorker = async (date, worker_id) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`, `SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`,
[date, worker_id] [date, worker_id]
); );
callback(null, rows.length > 0); return rows.length > 0;
} catch (err) {
callback(err);
}
}; };
// ✅ 내보내기
module.exports = { module.exports = {
create, create,
createBatch, createBatch,

View File

@@ -2,11 +2,7 @@ const { getDb } = require('../dbPool');
// ==================== 카테고리(공장) 관련 ==================== // ==================== 카테고리(공장) 관련 ====================
/** const createCategory = async (category) => {
* 카테고리 생성
*/
const createCategory = async (category, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
category_name, category_name,
@@ -22,34 +18,20 @@ const createCategory = async (category, callback) => {
[category_name, description, display_order, is_active] [category_name, description, display_order, is_active]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllCategories = async () => {
* 모든 카테고리 조회
*/
const getAllCategories = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
FROM workplace_categories FROM workplace_categories
ORDER BY display_order ASC, category_id ASC` ORDER BY display_order ASC, category_id ASC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getActiveCategories = async () => {
* 활성 카테고리만 조회
*/
const getActiveCategories = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
@@ -57,17 +39,10 @@ const getActiveCategories = async (callback) => {
WHERE is_active = TRUE WHERE is_active = TRUE
ORDER BY display_order ASC, category_id ASC` ORDER BY display_order ASC, category_id ASC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getCategoryById = async (categoryId) => {
* ID로 카테고리 조회
*/
const getCategoryById = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
@@ -75,17 +50,10 @@ const getCategoryById = async (categoryId, callback) => {
WHERE category_id = ?`, WHERE category_id = ?`,
[categoryId] [categoryId]
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** const updateCategory = async (categoryId, category) => {
* 카테고리 수정
*/
const updateCategory = async (categoryId, category, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
category_name, category_name,
@@ -102,35 +70,21 @@ const updateCategory = async (categoryId, category, callback) => {
[category_name, description, display_order, is_active, layout_image, categoryId] [category_name, description, display_order, is_active, layout_image, categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteCategory = async (categoryId) => {
* 카테고리 삭제
*/
const deleteCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM workplace_categories WHERE category_id = ?`, `DELETE FROM workplace_categories WHERE category_id = ?`,
[categoryId] [categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
// ==================== 작업장 관련 ==================== // ==================== 작업장 관련 ====================
/** const createWorkplace = async (workplace) => {
* 작업장 생성
*/
const createWorkplace = async (workplace, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
category_id = null, category_id = null,
@@ -148,17 +102,10 @@ const createWorkplace = async (workplace, callback) => {
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority] [category_id, workplace_name, description, is_active, workplace_purpose, display_priority]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllWorkplaces = async () => {
* 모든 작업장 조회 (카테고리 정보 포함)
*/
const getAllWorkplaces = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
@@ -168,17 +115,10 @@ const getAllWorkplaces = async (callback) => {
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC` ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getActiveWorkplaces = async () => {
* 활성 작업장만 조회
*/
const getActiveWorkplaces = async (callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
@@ -189,17 +129,10 @@ const getActiveWorkplaces = async (callback) => {
WHERE w.is_active = TRUE WHERE w.is_active = TRUE
ORDER BY wc.display_order ASC, w.workplace_id DESC` ORDER BY wc.display_order ASC, w.workplace_id DESC`
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getWorkplacesByCategory = async (categoryId) => {
* 카테고리별 작업장 조회
*/
const getWorkplacesByCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
@@ -211,17 +144,10 @@ const getWorkplacesByCategory = async (categoryId, callback) => {
ORDER BY w.workplace_id DESC`, ORDER BY w.workplace_id DESC`,
[categoryId] [categoryId]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getWorkplaceById = async (workplaceId) => {
* ID로 작업장 조회
*/
const getWorkplaceById = async (workplaceId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
@@ -232,17 +158,10 @@ const getWorkplaceById = async (workplaceId, callback) => {
WHERE w.workplace_id = ?`, WHERE w.workplace_id = ?`,
[workplaceId] [workplaceId]
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** const updateWorkplace = async (workplaceId, workplace) => {
* 작업장 수정
*/
const updateWorkplace = async (workplaceId, workplace, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
category_id, category_id,
@@ -262,35 +181,21 @@ const updateWorkplace = async (workplaceId, workplace, callback) => {
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId] [category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteWorkplace = async (workplaceId) => {
* 작업장 삭제
*/
const deleteWorkplace = async (workplaceId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM workplaces WHERE workplace_id = ?`, `DELETE FROM workplaces WHERE workplace_id = ?`,
[workplaceId] [workplaceId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
// ==================== 작업장 지도 영역 관련 ==================== // ==================== 작업장 지도 영역 관련 ====================
/** const createMapRegion = async (region) => {
* 작업장 지도 영역 생성
*/
const createMapRegion = async (region, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
workplace_id, workplace_id,
@@ -310,17 +215,10 @@ const createMapRegion = async (region, callback) => {
[workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points] [workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getMapRegionsByCategory = async (categoryId) => {
* 카테고리(공장)별 지도 영역 조회
*/
const getMapRegionsByCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT mr.*, w.workplace_name, w.description `SELECT mr.*, w.workplace_name, w.description
@@ -330,33 +228,19 @@ const getMapRegionsByCategory = async (categoryId, callback) => {
ORDER BY mr.region_id ASC`, ORDER BY mr.region_id ASC`,
[categoryId] [categoryId]
); );
callback(null, rows); return rows;
} catch (err) {
callback(err);
}
}; };
/** const getMapRegionByWorkplace = async (workplaceId) => {
* 작업장별 지도 영역 조회
*/
const getMapRegionByWorkplace = async (workplaceId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [rows] = await db.query( const [rows] = await db.query(
`SELECT * FROM workplace_map_regions WHERE workplace_id = ?`, `SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
[workplaceId] [workplaceId]
); );
callback(null, rows[0]); return rows[0];
} catch (err) {
callback(err);
}
}; };
/** const updateMapRegion = async (regionId, region) => {
* 지도 영역 수정
*/
const updateMapRegion = async (regionId, region, callback) => {
try {
const db = await getDb(); const db = await getDb();
const { const {
x_start, x_start,
@@ -374,54 +258,34 @@ const updateMapRegion = async (regionId, region, callback) => {
[x_start, y_start, x_end, y_end, shape, polygon_points, regionId] [x_start, y_start, x_end, y_end, shape, polygon_points, regionId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteMapRegion = async (regionId) => {
* 지도 영역 삭제
*/
const deleteMapRegion = async (regionId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM workplace_map_regions WHERE region_id = ?`, `DELETE FROM workplace_map_regions WHERE region_id = ?`,
[regionId] [regionId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteMapRegionsByCategory = async (categoryId) => {
* 작업장 영역 일괄 삭제 (카테고리별)
*/
const deleteMapRegionsByCategory = async (categoryId, callback) => {
try {
const db = await getDb(); const db = await getDb();
const [result] = await db.query( const [result] = await db.query(
`DELETE FROM workplace_map_regions WHERE category_id = ?`, `DELETE FROM workplace_map_regions WHERE category_id = ?`,
[categoryId] [categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
module.exports = { module.exports = {
// 카테고리
createCategory, createCategory,
getAllCategories, getAllCategories,
getActiveCategories, getActiveCategories,
getCategoryById, getCategoryById,
updateCategory, updateCategory,
deleteCategory, deleteCategory,
// 작업장
createWorkplace, createWorkplace,
getAllWorkplaces, getAllWorkplaces,
getActiveWorkplaces, getActiveWorkplaces,
@@ -429,8 +293,6 @@ module.exports = {
getWorkplaceById, getWorkplaceById,
updateWorkplace, updateWorkplace,
deleteWorkplace, deleteWorkplace,
// 지도 영역
createMapRegion, createMapRegion,
getMapRegionsByCategory, getMapRegionsByCategory,
getMapRegionByWorkplace, getMapRegionByWorkplace,

View File

@@ -24,16 +24,10 @@ const createWorkReportService = async (reportData) => {
logger.info('작업 보고서 생성 요청', { count: reports.length }); logger.info('작업 보고서 생성 요청', { count: reports.length });
const workReport_ids = [];
try { try {
const workReport_ids = [];
for (const report of reports) { for (const report of reports) {
const id = await new Promise((resolve, reject) => { const id = await workReportModel.create(report);
workReportModel.create(report, (err, insertId) => {
if (err) reject(err);
else resolve(insertId);
});
});
workReport_ids.push(id); workReport_ids.push(id);
} }
@@ -66,15 +60,8 @@ const getWorkReportsByDateService = async (date) => {
logger.info('작업 보고서 날짜별 조회 요청', { date }); logger.info('작업 보고서 날짜별 조회 요청', { date });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getAllByDate(date);
workReportModel.getAllByDate(date, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
logger.info('작업 보고서 조회 성공', { date, count: rows.length }); logger.info('작업 보고서 조회 성공', { date, count: rows.length });
return rows; return rows;
} catch (error) { } catch (error) {
logger.error('작업 보고서 조회 실패', { date, error: error.message }); logger.error('작업 보고서 조회 실패', { date, error: error.message });
@@ -96,15 +83,8 @@ const getWorkReportsInRangeService = async (start, end) => {
logger.info('작업 보고서 기간별 조회 요청', { start, end }); logger.info('작업 보고서 기간별 조회 요청', { start, end });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getByRange(start, end);
workReportModel.getByRange(start, end, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
logger.info('작업 보고서 조회 성공', { start, end, count: rows.length }); logger.info('작업 보고서 조회 성공', { start, end, count: rows.length });
return rows; return rows;
} catch (error) { } catch (error) {
logger.error('작업 보고서 조회 실패', { start, end, error: error.message }); logger.error('작업 보고서 조회 실패', { start, end, error: error.message });
@@ -123,12 +103,7 @@ const getWorkReportByIdService = async (id) => {
logger.info('작업 보고서 조회 요청', { report_id: id }); logger.info('작업 보고서 조회 요청', { report_id: id });
try { try {
const row = await new Promise((resolve, reject) => { const row = await workReportModel.getById(id);
workReportModel.getById(id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (!row) { if (!row) {
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
@@ -136,13 +111,9 @@ const getWorkReportByIdService = async (id) => {
} }
logger.info('작업 보고서 조회 성공', { report_id: id }); logger.info('작업 보고서 조회 성공', { report_id: id });
return row; return row;
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다');
} }
@@ -159,12 +130,7 @@ const updateWorkReportService = async (id, updateData) => {
logger.info('작업 보고서 수정 요청', { report_id: id }); logger.info('작업 보고서 수정 요청', { report_id: id });
try { try {
const changes = await new Promise((resolve, reject) => { const changes = await workReportModel.update(id, updateData);
workReportModel.update(id, updateData, (err, affectedRows) => {
if (err) reject(err);
else resolve(affectedRows);
});
});
if (changes === 0) { if (changes === 0) {
logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id });
@@ -172,13 +138,9 @@ const updateWorkReportService = async (id, updateData) => {
} }
logger.info('작업 보고서 수정 성공', { report_id: id, changes }); logger.info('작업 보고서 수정 성공', { report_id: id, changes });
return { changes }; return { changes };
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다');
} }
@@ -195,12 +157,7 @@ const removeWorkReportService = async (id) => {
logger.info('작업 보고서 삭제 요청', { report_id: id }); logger.info('작업 보고서 삭제 요청', { report_id: id });
try { try {
const changes = await new Promise((resolve, reject) => { const changes = await workReportModel.remove(id);
workReportModel.remove(id, (err, affectedRows) => {
if (err) reject(err);
else resolve(affectedRows);
});
});
if (changes === 0) { if (changes === 0) {
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
@@ -208,13 +165,9 @@ const removeWorkReportService = async (id) => {
} }
logger.info('작업 보고서 삭제 성공', { report_id: id, changes }); logger.info('작업 보고서 삭제 성공', { report_id: id, changes });
return { changes }; return { changes };
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다');
} }
@@ -237,35 +190,18 @@ const getSummaryService = async (year, month) => {
logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end }); logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getByRange(start, end);
workReportModel.getByRange(start, end, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
logger.warn('월간 요약 데이터 없음', { year, month }); logger.warn('월간 요약 데이터 없음', { year, month });
throw new NotFoundError('해당 기간의 작업 보고서가 없습니다'); throw new NotFoundError('해당 기간의 작업 보고서가 없습니다');
} }
logger.info('작업 보고서 월간 요약 조회 성공', { logger.info('작업 보고서 월간 요약 조회 성공', { year, month, count: rows.length });
year,
month,
count: rows.length
});
return rows; return rows;
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error; logger.error('작업 보고서 월간 요약 조회 실패', { year, month, error: error.message });
}
logger.error('작업 보고서 월간 요약 조회 실패', {
year,
month,
error: error.message
});
throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다'); throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다');
} }
}; };
@@ -274,7 +210,6 @@ const getSummaryService = async (year, month) => {
/** /**
* 작업 보고서의 부적합 원인 목록 조회 * 작업 보고서의 부적합 원인 목록 조회
* - error_type_id 또는 issue_report_id 중 하나로 연결
*/ */
const getReportDefectsService = async (reportId) => { const getReportDefectsService = async (reportId) => {
const db = await getDb(); const db = await getDb();
@@ -313,22 +248,16 @@ const getReportDefectsService = async (reportId) => {
/** /**
* 부적합 원인 저장 (전체 교체) * 부적합 원인 저장 (전체 교체)
* - error_type_id 또는 issue_report_id 중 하나 사용 가능
* - issue_report_id: 신고된 이슈와 연결
* - error_type_id: 기존 오류 유형 (레거시)
*/ */
const saveReportDefectsService = async (reportId, defects) => { const saveReportDefectsService = async (reportId, defects) => {
const db = await getDb(); const db = await getDb();
try { try {
await db.query('START TRANSACTION'); await db.query('START TRANSACTION');
// 기존 부적합 원인 삭제
await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]); await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]);
// 새 부적합 원인 추가
if (defects && defects.length > 0) { if (defects && defects.length > 0) {
for (const defect of defects) { for (const defect of defects) {
// issue_report_id > category_id/item_id > error_type_id 순으로 우선
await db.execute(` await db.execute(`
INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note) INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -344,12 +273,10 @@ const saveReportDefectsService = async (reportId, defects) => {
} }
} }
// 총 부적합 시간 계산 및 daily_work_reports 업데이트
const totalErrorHours = defects const totalErrorHours = defects
? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0) ? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0)
: 0; : 0;
// 첫 번째 defect의 error_type_id를 대표값으로 (레거시 호환)
const firstErrorTypeId = defects && defects.length > 0 const firstErrorTypeId = defects && defects.length > 0
? (defects.find(d => d.error_type_id)?.error_type_id || null) ? (defects.find(d => d.error_type_id)?.error_type_id || null)
: null; : null;
@@ -380,7 +307,6 @@ const saveReportDefectsService = async (reportId, defects) => {
/** /**
* 부적합 원인 추가 (단일) * 부적합 원인 추가 (단일)
* - issue_report_id 또는 error_type_id 중 하나 사용
*/ */
const addReportDefectService = async (reportId, defectData) => { const addReportDefectService = async (reportId, defectData) => {
const db = await getDb(); const db = await getDb();
@@ -396,7 +322,6 @@ const addReportDefectService = async (reportId, defectData) => {
defectData.note || null defectData.note || null
]); ]);
// 총 부적합 시간 업데이트
await updateTotalErrorHours(reportId); await updateTotalErrorHours(reportId);
logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId }); logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId });
@@ -413,7 +338,6 @@ const addReportDefectService = async (reportId, defectData) => {
const removeReportDefectService = async (defectId) => { const removeReportDefectService = async (defectId) => {
const db = await getDb(); const db = await getDb();
try { try {
// report_id 먼저 조회
const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]); const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]);
if (defect.length === 0) { if (defect.length === 0) {
throw new NotFoundError('부적합 원인을 찾을 수 없습니다'); throw new NotFoundError('부적합 원인을 찾을 수 없습니다');
@@ -421,10 +345,7 @@ const removeReportDefectService = async (defectId) => {
const reportId = defect[0].report_id; const reportId = defect[0].report_id;
// 삭제
await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]); await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]);
// 총 부적합 시간 업데이트
await updateTotalErrorHours(reportId); await updateTotalErrorHours(reportId);
logger.info('부적합 원인 삭제 성공', { defectId, reportId }); logger.info('부적합 원인 삭제 성공', { defectId, reportId });
@@ -438,7 +359,6 @@ const removeReportDefectService = async (defectId) => {
/** /**
* 총 부적합 시간 업데이트 헬퍼 * 총 부적합 시간 업데이트 헬퍼
* - issue_report_id가 있는 경우도 고려
*/ */
const updateTotalErrorHours = async (reportId) => { const updateTotalErrorHours = async (reportId) => {
const db = await getDb(); const db = await getDb();
@@ -450,8 +370,6 @@ const updateTotalErrorHours = async (reportId) => {
const totalErrorHours = result[0].total || 0; const totalErrorHours = result[0].total || 0;
// 첫 번째 부적합 원인의 error_type_id를 대표값으로 사용 (레거시 호환)
// issue_report_id만 있는 경우 error_type_id는 null
const [firstDefect] = await db.execute(` const [firstDefect] = await db.execute(`
SELECT error_type_id FROM work_report_defects SELECT error_type_id FROM work_report_defects
WHERE report_id = ? AND error_type_id IS NOT NULL WHERE report_id = ? AND error_type_id IS NOT NULL
@@ -480,7 +398,6 @@ module.exports = {
updateWorkReportService, updateWorkReportService,
removeWorkReportService, removeWorkReportService,
getSummaryService, getSummaryService,
// 부적합 원인 관리
getReportDefectsService, getReportDefectsService,
saveReportDefectsService, saveReportDefectsService,
addReportDefectService, addReportDefectService,

View File

@@ -19,7 +19,6 @@ describe('WorkReportService', () => {
describe('createWorkReportService', () => { describe('createWorkReportService', () => {
it('단일 보고서를 성공적으로 생성해야 함', async () => { it('단일 보고서를 성공적으로 생성해야 함', async () => {
// Arrange
const reportData = { const reportData = {
report_date: '2025-12-11', report_date: '2025-12-11',
worker_id: 1, worker_id: 1,
@@ -29,46 +28,32 @@ describe('WorkReportService', () => {
work_content: '기능 개발' work_content: '기능 개발'
}; };
// workReportModel.create가 콜백 형태이므로 모킹 설정 workReportModel.create.mockResolvedValue(123);
workReportModel.create = jest.fn((data, callback) => {
callback(null, 123); // insertId = 123
});
// Act
const result = await workReportService.createWorkReportService(reportData); const result = await workReportService.createWorkReportService(reportData);
// Assert
expect(result).toEqual({ workReport_ids: [123] }); expect(result).toEqual({ workReport_ids: [123] });
expect(workReportModel.create).toHaveBeenCalledTimes(1); expect(workReportModel.create).toHaveBeenCalledTimes(1);
expect(workReportModel.create).toHaveBeenCalledWith( expect(workReportModel.create).toHaveBeenCalledWith(reportData);
reportData,
expect.any(Function)
);
}); });
it('다중 보고서를 성공적으로 생성해야 함', async () => { it('다중 보고서를 성공적으로 생성해야 함', async () => {
// Arrange
const reportsData = [ const reportsData = [
{ report_date: '2025-12-11', worker_id: 1, work_hours: 8 }, { report_date: '2025-12-11', worker_id: 1, work_hours: 8 },
{ report_date: '2025-12-11', worker_id: 2, work_hours: 7 } { report_date: '2025-12-11', worker_id: 2, work_hours: 7 }
]; ];
let callCount = 0; workReportModel.create
workReportModel.create = jest.fn((data, callback) => { .mockResolvedValueOnce(101)
callCount++; .mockResolvedValueOnce(102);
callback(null, 100 + callCount);
});
// Act
const result = await workReportService.createWorkReportService(reportsData); const result = await workReportService.createWorkReportService(reportsData);
// Assert
expect(result).toEqual({ workReport_ids: [101, 102] }); expect(result).toEqual({ workReport_ids: [101, 102] });
expect(workReportModel.create).toHaveBeenCalledTimes(2); expect(workReportModel.create).toHaveBeenCalledTimes(2);
}); });
it('빈 배열이면 ValidationError를 던져야 함', async () => { it('빈 배열이면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.createWorkReportService([])) await expect(workReportService.createWorkReportService([]))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
@@ -77,14 +62,10 @@ describe('WorkReportService', () => {
}); });
it('DB 오류 시 DatabaseError를 던져야 함', async () => { it('DB 오류 시 DatabaseError를 던져야 함', async () => {
// Arrange
const reportData = { report_date: '2025-12-11', worker_id: 1 }; const reportData = { report_date: '2025-12-11', worker_id: 1 };
workReportModel.create = jest.fn((data, callback) => { workReportModel.create.mockRejectedValue(new Error('DB connection failed'));
callback(new Error('DB connection failed'), null);
});
// Act & Assert
await expect(workReportService.createWorkReportService(reportData)) await expect(workReportService.createWorkReportService(reportData))
.rejects.toThrow(DatabaseError); .rejects.toThrow(DatabaseError);
}); });
@@ -92,24 +73,18 @@ describe('WorkReportService', () => {
describe('getWorkReportsByDateService', () => { describe('getWorkReportsByDateService', () => {
it('날짜로 보고서를 조회해야 함', async () => { it('날짜로 보고서를 조회해야 함', async () => {
// Arrange
const date = '2025-12-11'; const date = '2025-12-11';
const mockReports = mockWorkReports.filter(r => r.report_date === date); const mockReports = mockWorkReports.filter(r => r.report_date === date);
workReportModel.getAllByDate = jest.fn((date, callback) => { workReportModel.getAllByDate.mockResolvedValue(mockReports);
callback(null, mockReports);
});
// Act
const result = await workReportService.getWorkReportsByDateService(date); const result = await workReportService.getWorkReportsByDateService(date);
// Assert
expect(result).toEqual(mockReports); expect(result).toEqual(mockReports);
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date, expect.any(Function)); expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date);
}); });
it('날짜가 없으면 ValidationError를 던져야 함', async () => { it('날짜가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsByDateService(null)) await expect(workReportService.getWorkReportsByDateService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
@@ -118,12 +93,8 @@ describe('WorkReportService', () => {
}); });
it('DB 오류 시 DatabaseError를 던져야 함', async () => { it('DB 오류 시 DatabaseError를 던져야 함', async () => {
// Arrange workReportModel.getAllByDate.mockRejectedValue(new Error('DB error'));
workReportModel.getAllByDate = jest.fn((date, callback) => {
callback(new Error('DB error'), null);
});
// Act & Assert
await expect(workReportService.getWorkReportsByDateService('2025-12-11')) await expect(workReportService.getWorkReportsByDateService('2025-12-11'))
.rejects.toThrow(DatabaseError); .rejects.toThrow(DatabaseError);
}); });
@@ -131,28 +102,19 @@ describe('WorkReportService', () => {
describe('getWorkReportByIdService', () => { describe('getWorkReportByIdService', () => {
it('ID로 보고서를 조회해야 함', async () => { it('ID로 보고서를 조회해야 함', async () => {
// Arrange
const mockReport = mockWorkReports[0]; const mockReport = mockWorkReports[0];
workReportModel.getById = jest.fn((id, callback) => { workReportModel.getById.mockResolvedValue(mockReport);
callback(null, mockReport);
});
// Act
const result = await workReportService.getWorkReportByIdService(1); const result = await workReportService.getWorkReportByIdService(1);
// Assert
expect(result).toEqual(mockReport); expect(result).toEqual(mockReport);
expect(workReportModel.getById).toHaveBeenCalledWith(1, expect.any(Function)); expect(workReportModel.getById).toHaveBeenCalledWith(1);
}); });
it('보고서가 없으면 NotFoundError를 던져야 함', async () => { it('보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.getById.mockResolvedValue(null);
workReportModel.getById = jest.fn((id, callback) => {
callback(null, null);
});
// Act & Assert
await expect(workReportService.getWorkReportByIdService(999)) await expect(workReportService.getWorkReportByIdService(999))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
@@ -161,7 +123,6 @@ describe('WorkReportService', () => {
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportByIdService(null)) await expect(workReportService.getWorkReportByIdService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -169,38 +130,24 @@ describe('WorkReportService', () => {
describe('updateWorkReportService', () => { describe('updateWorkReportService', () => {
it('보고서를 성공적으로 수정해야 함', async () => { it('보고서를 성공적으로 수정해야 함', async () => {
// Arrange
const updateData = { work_hours: 9 }; const updateData = { work_hours: 9 };
workReportModel.update = jest.fn((id, data, callback) => { workReportModel.update.mockResolvedValue(1);
callback(null, 1); // affectedRows = 1
});
// Act
const result = await workReportService.updateWorkReportService(123, updateData); const result = await workReportService.updateWorkReportService(123, updateData);
// Assert
expect(result).toEqual({ changes: 1 }); expect(result).toEqual({ changes: 1 });
expect(workReportModel.update).toHaveBeenCalledWith( expect(workReportModel.update).toHaveBeenCalledWith(123, updateData);
123,
updateData,
expect.any(Function)
);
}); });
it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => { it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.update.mockResolvedValue(0);
workReportModel.update = jest.fn((id, data, callback) => {
callback(null, 0); // affectedRows = 0
});
// Act & Assert
await expect(workReportService.updateWorkReportService(999, {})) await expect(workReportService.updateWorkReportService(999, {}))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.updateWorkReportService(null, {})) await expect(workReportService.updateWorkReportService(null, {}))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -208,32 +155,22 @@ describe('WorkReportService', () => {
describe('removeWorkReportService', () => { describe('removeWorkReportService', () => {
it('보고서를 성공적으로 삭제해야 함', async () => { it('보고서를 성공적으로 삭제해야 함', async () => {
// Arrange workReportModel.remove.mockResolvedValue(1);
workReportModel.remove = jest.fn((id, callback) => {
callback(null, 1);
});
// Act
const result = await workReportService.removeWorkReportService(123); const result = await workReportService.removeWorkReportService(123);
// Assert
expect(result).toEqual({ changes: 1 }); expect(result).toEqual({ changes: 1 });
expect(workReportModel.remove).toHaveBeenCalledWith(123, expect.any(Function)); expect(workReportModel.remove).toHaveBeenCalledWith(123);
}); });
it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => { it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.remove.mockResolvedValue(0);
workReportModel.remove = jest.fn((id, callback) => {
callback(null, 0);
});
// Act & Assert
await expect(workReportService.removeWorkReportService(999)) await expect(workReportService.removeWorkReportService(999))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.removeWorkReportService(null)) await expect(workReportService.removeWorkReportService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -241,34 +178,23 @@ describe('WorkReportService', () => {
describe('getWorkReportsInRangeService', () => { describe('getWorkReportsInRangeService', () => {
it('기간으로 보고서를 조회해야 함', async () => { it('기간으로 보고서를 조회해야 함', async () => {
// Arrange
const start = '2025-12-01'; const start = '2025-12-01';
const end = '2025-12-31'; const end = '2025-12-31';
workReportModel.getByRange = jest.fn((start, end, callback) => { workReportModel.getByRange.mockResolvedValue(mockWorkReports);
callback(null, mockWorkReports);
});
// Act
const result = await workReportService.getWorkReportsInRangeService(start, end); const result = await workReportService.getWorkReportsInRangeService(start, end);
// Assert
expect(result).toEqual(mockWorkReports); expect(result).toEqual(mockWorkReports);
expect(workReportModel.getByRange).toHaveBeenCalledWith( expect(workReportModel.getByRange).toHaveBeenCalledWith(start, end);
start,
end,
expect.any(Function)
);
}); });
it('시작일이 없으면 ValidationError를 던져야 함', async () => { it('시작일이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31')) await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31'))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('종료일이 없으면 ValidationError를 던져야 함', async () => { it('종료일이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null)) await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -276,41 +202,30 @@ describe('WorkReportService', () => {
describe('getSummaryService', () => { describe('getSummaryService', () => {
it('월간 요약을 조회해야 함', async () => { it('월간 요약을 조회해야 함', async () => {
// Arrange
const year = '2025'; const year = '2025';
const month = '12'; const month = '12';
workReportModel.getByRange = jest.fn((start, end, callback) => { workReportModel.getByRange.mockResolvedValue(mockWorkReports);
callback(null, mockWorkReports);
});
// Act
const result = await workReportService.getSummaryService(year, month); const result = await workReportService.getSummaryService(year, month);
// Assert
expect(result).toEqual(mockWorkReports); expect(result).toEqual(mockWorkReports);
expect(workReportModel.getByRange).toHaveBeenCalled(); expect(workReportModel.getByRange).toHaveBeenCalled();
}); });
it('연도가 없으면 ValidationError를 던져야 함', async () => { it('연도가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getSummaryService(null, '12')) await expect(workReportService.getSummaryService(null, '12'))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('월이 없으면 ValidationError를 던져야 함', async () => { it('월이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getSummaryService('2025', null)) await expect(workReportService.getSummaryService('2025', null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('데이터가 없으면 NotFoundError를 던져야 함', async () => { it('데이터가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.getByRange.mockResolvedValue([]);
workReportModel.getByRange = jest.fn((start, end, callback) => {
callback(null, []);
});
// Act & Assert
await expect(workReportService.getSummaryService('2025', '12')) await expect(workReportService.getSummaryService('2025', '12'))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);