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:
@@ -14,7 +14,7 @@ const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* 📝 작업보고서 생성 (V2 - Service Layer 사용)
|
||||
* 작업보고서 생성 (V2 - Service Layer 사용)
|
||||
*/
|
||||
const createDailyWorkReport = asyncHandler(async (req, res) => {
|
||||
const reportData = {
|
||||
@@ -33,29 +33,19 @@ const createDailyWorkReport = asyncHandler(async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* 📊 기여자별 요약 조회 (새로운 기능)
|
||||
* 기여자별 요약 조회
|
||||
*/
|
||||
const getContributorsSummary = asyncHandler(async (req, res) => {
|
||||
const { date, worker_id } = req.query;
|
||||
|
||||
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}`);
|
||||
|
||||
try {
|
||||
const data = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.getContributorsByDate(date, worker_id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const data = await dailyWorkReportModel.getContributorsByDate(date, worker_id);
|
||||
|
||||
const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
|
||||
|
||||
console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`);
|
||||
|
||||
const result = {
|
||||
date,
|
||||
worker_id,
|
||||
@@ -65,15 +55,12 @@ const getContributorsSummary = asyncHandler(async (req, res) => {
|
||||
};
|
||||
|
||||
res.success(result, '기여자별 요약 조회 성공');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '기여자별 요약 조회');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 📊 개인 누적 현황 조회 (새로운 기능)
|
||||
* 개인 누적 현황 조회
|
||||
*/
|
||||
const getMyAccumulatedData = (req, res) => {
|
||||
const getMyAccumulatedData = async (req, res) => {
|
||||
const { date, worker_id } = req.query;
|
||||
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({
|
||||
date,
|
||||
worker_id,
|
||||
@@ -109,13 +87,19 @@ const getMyAccumulatedData = (req, res) => {
|
||||
my_data: data,
|
||||
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 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({
|
||||
message: '항목이 성공적으로 삭제되었습니다.',
|
||||
id: id,
|
||||
@@ -144,17 +119,23 @@ const removeMyEntry = (req, res) => {
|
||||
timestamp: new Date().toISOString(),
|
||||
...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) => {
|
||||
try {
|
||||
const userInfo = {
|
||||
user_id: req.user?.user_id || req.user?.id,
|
||||
role: req.user?.role || 'user' // 기본값을 'user'로 설정하여 안전하게 처리
|
||||
role: req.user?.role || 'user'
|
||||
};
|
||||
|
||||
if (!userInfo.user_id) {
|
||||
@@ -166,7 +147,7 @@ const getDailyWorkReports = async (req, res) => {
|
||||
res.json(reports);
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 작업보고서 조회 컨트롤러 오류:', error.message);
|
||||
logger.error('작업보고서 조회 컨트롤러 오류:', error.message);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: '작업보고서 조회에 실패했습니다.',
|
||||
@@ -176,13 +157,11 @@ const getDailyWorkReports = async (req, res) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
|
||||
* 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
|
||||
*/
|
||||
const getDailyWorkReportsByDate = (req, res) => {
|
||||
const getDailyWorkReportsByDate = async (req, res) => {
|
||||
const { date } = req.params;
|
||||
const current_user_id = req.user?.user_id || req.user?.id;
|
||||
const user_access_level = req.user?.access_level;
|
||||
const user_job_type = req.user?.job_type;
|
||||
|
||||
if (!current_user_id) {
|
||||
return res.status(401).json({
|
||||
@@ -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);
|
||||
|
||||
dailyWorkReportModel.getByDate(date, (err, data) => {
|
||||
if (err) {
|
||||
console.error('날짜별 작업보고서 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
// 임시로 모든 사용자에게 전체 조회 허용
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
logger.error('날짜별 작업보고서 조회 오류:', err);
|
||||
res.status(500).json({
|
||||
error: '작업보고서 조회 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
|
||||
// 🎯 권한별 필터링 (임시로 비활성화)
|
||||
let finalData = data;
|
||||
console.log(`📊 임시로 모든 사용자에게 전체 조회 허용: ${data.length}개`);
|
||||
console.log(`📊 권한 정보: access_level=${user_access_level}, job_type=${user_job_type}, isAdmin=${isAdmin}`);
|
||||
|
||||
// if (!isAdmin) {
|
||||
// finalData = data.filter(report => report.created_by === current_user_id);
|
||||
// console.log(`📊 권한 필터링: 전체 ${data.length}개 → ${finalData.length}개`);
|
||||
// } else {
|
||||
// console.log(`📊 관리자 권한으로 전체 조회: ${data.length}개`);
|
||||
// }
|
||||
|
||||
res.json(finalData);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 🔍 작업보고서 검색 (페이지네이션 포함)
|
||||
* 작업보고서 검색 (페이지네이션 포함)
|
||||
*/
|
||||
const searchWorkReports = (req, res) => {
|
||||
const searchWorkReports = async (req, res) => {
|
||||
const { start_date, end_date, worker_id, project_id, work_status_id, page = 1, limit = 20 } = req.query;
|
||||
const created_by = req.user?.user_id || req.user?.id;
|
||||
|
||||
@@ -247,36 +210,32 @@ const searchWorkReports = (req, res) => {
|
||||
worker_id: worker_id ? parseInt(worker_id) : null,
|
||||
project_id: project_id ? parseInt(project_id) : null,
|
||||
work_status_id: work_status_id ? parseInt(work_status_id) : null,
|
||||
created_by, // 작성자 필터링 추가
|
||||
created_by,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit)
|
||||
};
|
||||
|
||||
console.log('🔍 작업보고서 검색 요청:', searchParams);
|
||||
|
||||
dailyWorkReportModel.searchWithDetails(searchParams, (err, data) => {
|
||||
if (err) {
|
||||
console.error('작업보고서 검색 오류:', err);
|
||||
return res.status(500).json({
|
||||
try {
|
||||
const data = await dailyWorkReportModel.searchWithDetails(searchParams);
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
logger.error('작업보고서 검색 오류:', err);
|
||||
res.status(500).json({
|
||||
error: '작업보고서 검색 중 오류가 발생했습니다.',
|
||||
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) => {
|
||||
try {
|
||||
const statsData = await dailyWorkReportService.getStatisticsService(req.query);
|
||||
res.json(statsData);
|
||||
} catch (error) {
|
||||
console.error('💥 통계 조회 컨트롤러 오류:', error.message);
|
||||
logger.error('통계 조회 컨트롤러 오류:', error.message);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: '통계 조회에 실패했습니다.',
|
||||
@@ -286,14 +245,14 @@ const getWorkReportStats = async (req, res) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 일일 근무 요약 조회 (V2 - Service Layer 사용)
|
||||
* 일일 근무 요약 조회 (V2 - Service Layer 사용)
|
||||
*/
|
||||
const getDailySummary = async (req, res) => {
|
||||
try {
|
||||
const summaryData = await dailyWorkReportService.getSummaryService(req.query);
|
||||
res.json(summaryData);
|
||||
} catch (error) {
|
||||
console.error('💥 일일 요약 조회 컨트롤러 오류:', error.message);
|
||||
logger.error('일일 요약 조회 컨트롤러 오류:', error.message);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: '일일 요약 조회에 실패했습니다.',
|
||||
@@ -303,9 +262,9 @@ const getDailySummary = async (req, res) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 📅 월간 요약 조회
|
||||
* 월간 요약 조회
|
||||
*/
|
||||
const getMonthlySummary = (req, res) => {
|
||||
const getMonthlySummary = async (req, res) => {
|
||||
const { year, month } = req.query;
|
||||
|
||||
if (!year || !month) {
|
||||
@@ -316,16 +275,8 @@ const getMonthlySummary = (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`📅 월간 요약 조회: ${year}-${month}`);
|
||||
|
||||
dailyWorkReportModel.getMonthlySummary(year, month, (err, data) => {
|
||||
if (err) {
|
||||
console.error('월간 요약 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
error: '월간 요약 조회 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
try {
|
||||
const data = await dailyWorkReportModel.getMonthlySummary(year, month);
|
||||
|
||||
res.json({
|
||||
year: parseInt(year),
|
||||
@@ -334,11 +285,17 @@ const getMonthlySummary = (req, res) => {
|
||||
total_entries: data.length,
|
||||
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) => {
|
||||
try {
|
||||
@@ -362,7 +319,7 @@ const updateWorkReport = async (req, res) => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`💥 작업보고서 수정 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
|
||||
logger.error(`작업보고서 수정 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
|
||||
const statusCode = error.statusCode || 400;
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
@@ -373,7 +330,7 @@ const updateWorkReport = async (req, res) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 🗑️ 특정 작업보고서 삭제 (V2 - Service Layer 사용)
|
||||
* 특정 작업보고서 삭제 (V2 - Service Layer 사용)
|
||||
* 권한: 그룹장(group_leader), 시스템(system), 관리자(admin)만 가능
|
||||
*/
|
||||
const removeDailyWorkReport = async (req, res) => {
|
||||
@@ -406,7 +363,7 @@ const removeDailyWorkReport = async (req, res) => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`💥 작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
|
||||
logger.error(`작업보고서 삭제 컨트롤러 오류 (id: ${req.params.id}):`, error.message);
|
||||
const statusCode = error.statusCode || 400;
|
||||
res.status(statusCode).json({
|
||||
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 deleted_by = req.user?.user_id || req.user?.id;
|
||||
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}`);
|
||||
|
||||
dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by, (err, affectedRows) => {
|
||||
if (err) {
|
||||
console.error('작업보고서 전체 삭제 오류:', err);
|
||||
return res.status(500).json({
|
||||
error: '작업보고서 삭제 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
try {
|
||||
const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by);
|
||||
|
||||
if (affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
@@ -458,7 +407,6 @@ const removeDailyWorkReportByDateAndWorker = (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ 날짜+작업자별 전체 삭제 완료: ${affectedRows}개`);
|
||||
res.json({
|
||||
message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`,
|
||||
date,
|
||||
@@ -467,18 +415,29 @@ const removeDailyWorkReportByDateAndWorker = (req, res) => {
|
||||
deleted_by,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('작업보고서 전체 삭제 오류:', err);
|
||||
res.status(500).json({
|
||||
error: '작업보고서 삭제 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 📋 마스터 데이터 조회 함수들
|
||||
* 마스터 데이터 조회 함수들
|
||||
*/
|
||||
const getWorkTypes = (req, res) => {
|
||||
console.log('📋 작업 유형 조회 요청');
|
||||
dailyWorkReportModel.getAllWorkTypes((err, data) => {
|
||||
if (err) {
|
||||
console.error('작업 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
const getWorkTypes = async (req, res) => {
|
||||
try {
|
||||
const data = await dailyWorkReportModel.getAllWorkTypes();
|
||||
res.json({
|
||||
success: true,
|
||||
data: data,
|
||||
message: '작업 유형 조회 성공'
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('작업 유형 조회 오류:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: '작업 유형 조회 중 오류가 발생했습니다.',
|
||||
@@ -486,316 +445,203 @@ const getWorkTypes = (req, res) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`📋 작업 유형 조회 결과: ${data.length}개`);
|
||||
res.json({
|
||||
success: true,
|
||||
data: data,
|
||||
message: '작업 유형 조회 성공'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getWorkStatusTypes = (req, res) => {
|
||||
console.log('📋 업무 상태 유형 조회 요청');
|
||||
dailyWorkReportModel.getAllWorkStatusTypes((err, data) => {
|
||||
if (err) {
|
||||
console.error('업무 상태 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
const getWorkStatusTypes = async (req, res) => {
|
||||
try {
|
||||
const data = await dailyWorkReportModel.getAllWorkStatusTypes();
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
logger.error('업무 상태 유형 조회 오류:', err);
|
||||
res.status(500).json({
|
||||
error: '업무 상태 유형 조회 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
console.log(`📋 업무 상태 유형 조회 결과: ${data.length}개`);
|
||||
res.json(data);
|
||||
});
|
||||
};
|
||||
|
||||
const getErrorTypes = (req, res) => {
|
||||
console.log('📋 에러 유형 조회 요청');
|
||||
dailyWorkReportModel.getAllErrorTypes((err, data) => {
|
||||
if (err) {
|
||||
console.error('에러 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
const getErrorTypes = async (req, res) => {
|
||||
try {
|
||||
const data = await dailyWorkReportModel.getAllErrorTypes();
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
logger.error('에러 유형 조회 오류:', err);
|
||||
res.status(500).json({
|
||||
error: '에러 유형 조회 중 오류가 발생했습니다.',
|
||||
details: err.message
|
||||
});
|
||||
}
|
||||
console.log(`📋 에러 유형 조회 결과: ${data.length}개`);
|
||||
res.json(data);
|
||||
});
|
||||
};
|
||||
|
||||
// ========== 작업 유형 CRUD ==========
|
||||
|
||||
/**
|
||||
* 📝 작업 유형 생성
|
||||
* 작업 유형 생성
|
||||
*/
|
||||
const createWorkType = asyncHandler(async (req, res) => {
|
||||
const { name, description, category } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new ApiError('작업 유형 이름이 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 유형 이름이 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('📝 작업 유형 생성:', { name, description, category });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.createWorkType({ name, description, category }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const result = await dailyWorkReportModel.createWorkType({ name, description, category });
|
||||
res.created(result, '작업 유형이 성공적으로 생성되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 유형 생성');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ✏️ 작업 유형 수정
|
||||
* 작업 유형 수정
|
||||
*/
|
||||
const updateWorkType = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, description, category } = req.body;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('작업 유형 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('✏️ 작업 유형 수정:', { id, name, description, category });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.updateWorkType(id, { name, description, category }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.updateWorkType(id, { name, description, category });
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('수정할 작업 유형을 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '수정할 작업 유형을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '작업 유형이 성공적으로 수정되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 유형 수정');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 🗑️ 작업 유형 삭제
|
||||
* 작업 유형 삭제
|
||||
*/
|
||||
const deleteWorkType = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('작업 유형 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 유형 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('🗑️ 작업 유형 삭제:', id);
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.deleteWorkType(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.deleteWorkType(id);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('삭제할 작업 유형을 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '삭제할 작업 유형을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '작업 유형이 성공적으로 삭제되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 유형 삭제');
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 작업 상태 CRUD ==========
|
||||
|
||||
/**
|
||||
* 📝 작업 상태 생성
|
||||
* 작업 상태 생성
|
||||
*/
|
||||
const createWorkStatus = asyncHandler(async (req, res) => {
|
||||
const { name, description, is_error } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new ApiError('작업 상태 이름이 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 상태 이름이 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('📝 작업 상태 생성:', { name, description, is_error });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.createWorkStatus({ name, description, is_error }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const result = await dailyWorkReportModel.createWorkStatus({ name, description, is_error });
|
||||
res.created(result, '작업 상태가 성공적으로 생성되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 상태 생성');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ✏️ 작업 상태 수정
|
||||
* 작업 상태 수정
|
||||
*/
|
||||
const updateWorkStatus = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, description, is_error } = req.body;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('작업 상태 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('✏️ 작업 상태 수정:', { id, name, description, is_error });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.updateWorkStatus(id, { name, description, is_error }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.updateWorkStatus(id, { name, description, is_error });
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('수정할 작업 상태를 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '수정할 작업 상태를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '작업 상태가 성공적으로 수정되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 상태 수정');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 🗑️ 작업 상태 삭제
|
||||
* 작업 상태 삭제
|
||||
*/
|
||||
const deleteWorkStatus = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('작업 상태 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '작업 상태 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('🗑️ 작업 상태 삭제:', id);
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.deleteWorkStatus(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.deleteWorkStatus(id);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('삭제할 작업 상태를 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '삭제할 작업 상태를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '작업 상태가 성공적으로 삭제되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '작업 상태 삭제');
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 오류 유형 CRUD ==========
|
||||
|
||||
/**
|
||||
* 📝 오류 유형 생성
|
||||
* 오류 유형 생성
|
||||
*/
|
||||
const createErrorType = asyncHandler(async (req, res) => {
|
||||
const { name, description, severity } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new ApiError('오류 유형 이름이 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '오류 유형 이름이 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('📝 오류 유형 생성:', { name, description, severity });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.createErrorType({ name, description, severity }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const result = await dailyWorkReportModel.createErrorType({ name, description, severity });
|
||||
res.created(result, '오류 유형이 성공적으로 생성되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '오류 유형 생성');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ✏️ 오류 유형 수정
|
||||
* 오류 유형 수정
|
||||
*/
|
||||
const updateErrorType = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, description, severity } = req.body;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('오류 유형 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('✏️ 오류 유형 수정:', { id, name, description, severity });
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.updateErrorType(id, { name, description, severity }, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.updateErrorType(id, { name, description, severity });
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('수정할 오류 유형을 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '수정할 오류 유형을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '오류 유형이 성공적으로 수정되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '오류 유형 수정');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 🗑️ 오류 유형 삭제
|
||||
* 오류 유형 삭제
|
||||
*/
|
||||
const deleteErrorType = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError('오류 유형 ID가 필요합니다.', 400);
|
||||
return res.status(400).json({ error: '오류 유형 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
console.log('🗑️ 오류 유형 삭제:', id);
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
dailyWorkReportModel.deleteErrorType(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const result = await dailyWorkReportModel.deleteErrorType(id);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
throw new ApiError('삭제할 오류 유형을 찾을 수 없습니다.', 404);
|
||||
return res.status(404).json({ error: '삭제할 오류 유형을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.success(result, '오류 유형이 성공적으로 삭제되었습니다.');
|
||||
} catch (err) {
|
||||
handleDatabaseError(err, '오류 유형 삭제');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 📊 누적 현황 조회
|
||||
* 누적 현황 조회
|
||||
*/
|
||||
const getAccumulatedReports = (req, res) => {
|
||||
const getAccumulatedReports = async (req, res) => {
|
||||
const { date, worker_id } = req.query;
|
||||
|
||||
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({
|
||||
date,
|
||||
worker_id,
|
||||
@@ -824,7 +661,13 @@ const getAccumulatedReports = (req, res) => {
|
||||
accumulated_data: data,
|
||||
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,
|
||||
error_hours: error_hours || 0,
|
||||
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,
|
||||
created_by: req.user.user_id
|
||||
};
|
||||
@@ -884,31 +727,29 @@ const createFromTbm = async (req, res) => {
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('TBM 작업보고서 생성 오류:', err);
|
||||
console.error('Error stack:', err.stack);
|
||||
logger.error('TBM 작업보고서 생성 오류:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'TBM 작업보고서 생성 중 오류가 발생했습니다.',
|
||||
error: err.message,
|
||||
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 모든 컨트롤러 함수 내보내기 (리팩토링된 함수 위주로 재구성)
|
||||
// 모든 컨트롤러 함수 내보내기
|
||||
module.exports = {
|
||||
// 📝 V2 핵심 CRUD 함수
|
||||
// V2 핵심 CRUD 함수
|
||||
createDailyWorkReport,
|
||||
getDailyWorkReports,
|
||||
updateWorkReport,
|
||||
removeDailyWorkReport,
|
||||
createFromTbm,
|
||||
|
||||
// 📊 V2 통계 및 요약 함수
|
||||
// V2 통계 및 요약 함수
|
||||
getWorkReportStats,
|
||||
getDailySummary,
|
||||
|
||||
// 🔽 아직 리팩토링되지 않은 레거시 함수들
|
||||
// 레거시 함수 (콜백 제거 완료)
|
||||
getAccumulatedReports,
|
||||
getContributorsSummary,
|
||||
getMyAccumulatedData,
|
||||
@@ -921,7 +762,7 @@ module.exports = {
|
||||
getWorkStatusTypes,
|
||||
getErrorTypes,
|
||||
|
||||
// 🔽 마스터 데이터 CRUD
|
||||
// 마스터 데이터 CRUD
|
||||
createWorkType,
|
||||
updateWorkType,
|
||||
deleteWorkType,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
|
||||
const vacationBalanceModel = require('../models/vacationBalanceModel');
|
||||
const vacationTypeModel = require('../models/vacationTypeModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const vacationBalanceController = {
|
||||
/**
|
||||
@@ -14,27 +15,11 @@ const vacationBalanceController = {
|
||||
async getByWorkerAndYear(req, res) {
|
||||
try {
|
||||
const { workerId, year } = req.params;
|
||||
|
||||
vacationBalanceModel.getByWorkerAndYear(workerId, year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getByWorkerAndYear(workerId, year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getByWorkerAndYear 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '휴가 잔액을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,27 +30,11 @@ const vacationBalanceController = {
|
||||
async getAllByYear(req, res) {
|
||||
try {
|
||||
const { year } = req.params;
|
||||
|
||||
vacationBalanceModel.getAllByYear(year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('전체 휴가 잔액 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getAllByYear(year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAllByYear 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('전체 휴가 잔액 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -75,17 +44,9 @@ const vacationBalanceController = {
|
||||
*/
|
||||
async createBalance(req, res) {
|
||||
try {
|
||||
const {
|
||||
worker_id,
|
||||
vacation_type_id,
|
||||
year,
|
||||
total_days,
|
||||
used_days,
|
||||
notes
|
||||
} = req.body;
|
||||
const { worker_id, vacation_type_id, year, total_days, used_days, notes } = req.body;
|
||||
const created_by = req.user.user_id;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!worker_id || !vacation_type_id || !year || total_days === undefined) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -94,15 +55,7 @@ const vacationBalanceController = {
|
||||
}
|
||||
|
||||
// 중복 체크
|
||||
vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year, (err, existing) => {
|
||||
if (err) {
|
||||
console.error('중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '중복 체크 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year);
|
||||
if (existing && existing.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -120,28 +73,15 @@ const vacationBalanceController = {
|
||||
created_by
|
||||
};
|
||||
|
||||
vacationBalanceModel.create(balanceData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await vacationBalanceModel.create(balanceData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 생성되었습니다',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('createBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 생성 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -161,39 +101,18 @@ const vacationBalanceController = {
|
||||
updateData.updated_at = new Date();
|
||||
|
||||
if (Object.keys(updateData).length === 1) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '수정할 데이터가 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
vacationBalanceModel.update(id, updateData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 수정 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 수정하는 중 오류가 발생했습니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
const result = await vacationBalanceModel.update(id, updateData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 찾을 수 없습니다'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 수정되었습니다'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '휴가 잔액이 수정되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('updateBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 수정 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -204,34 +123,16 @@ const vacationBalanceController = {
|
||||
async deleteBalance(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
vacationBalanceModel.delete(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 삭제 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 삭제하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
const result = await vacationBalanceModel.delete(id);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 찾을 수 없습니다'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 삭제되었습니다'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '휴가 잔액이 삭제되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('deleteBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -251,31 +152,18 @@ const vacationBalanceController = {
|
||||
});
|
||||
}
|
||||
|
||||
// 연차 일수 계산
|
||||
const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year);
|
||||
|
||||
// ANNUAL 휴가 유형 ID 조회
|
||||
vacationTypeModel.getByCode('ANNUAL', (err, types) => {
|
||||
if (err || !types || types.length === 0) {
|
||||
console.error('ANNUAL 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'ANNUAL 휴가 유형을 찾을 수 없습니다'
|
||||
});
|
||||
const types = await vacationTypeModel.getByCode('ANNUAL');
|
||||
if (!types || types.length === 0) {
|
||||
return res.status(500).json({ success: false, message: 'ANNUAL 휴가 유형을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const annualTypeId = types[0].id;
|
||||
|
||||
// 중복 체크
|
||||
vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year, (err, existing) => {
|
||||
if (err) {
|
||||
console.error('중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '중복 체크 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year);
|
||||
if (existing && existing.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -293,32 +181,15 @@ const vacationBalanceController = {
|
||||
created_by
|
||||
};
|
||||
|
||||
vacationBalanceModel.create(balanceData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await vacationBalanceModel.create(balanceData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: `${annualDays}일의 연차가 자동으로 생성되었습니다`,
|
||||
data: {
|
||||
id: result.insertId,
|
||||
calculated_days: annualDays
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
data: { id: result.insertId, calculated_days: annualDays }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('autoCalculateAndCreate 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('연차 자동 계산 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -332,10 +203,7 @@ const vacationBalanceController = {
|
||||
const created_by = req.user.user_id;
|
||||
|
||||
if (!balances || !Array.isArray(balances) || balances.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '저장할 데이터가 없습니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '저장할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
@@ -353,7 +221,6 @@ const vacationBalanceController = {
|
||||
}
|
||||
|
||||
try {
|
||||
// Upsert 쿼리
|
||||
const query = `
|
||||
INSERT INTO vacation_balance_details
|
||||
(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]);
|
||||
successCount++;
|
||||
} catch (err) {
|
||||
console.error('휴가 잔액 저장 오류:', err);
|
||||
logger.error('휴가 잔액 저장 오류:', err);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
@@ -378,11 +245,8 @@ const vacationBalanceController = {
|
||||
data: { successCount, errorCount }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('bulkUpsert 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('bulkUpsert 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -393,27 +257,11 @@ const vacationBalanceController = {
|
||||
async getAvailableDays(req, res) {
|
||||
try {
|
||||
const { workerId, year } = req.params;
|
||||
|
||||
vacationBalanceModel.getAvailableVacationDays(workerId, year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('사용 가능 휴가 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getAvailableVacationDays(workerId, year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAvailableDays 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('사용 가능 휴가 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
const vacationTypeModel = require('../models/vacationTypeModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const vacationTypeController = {
|
||||
/**
|
||||
@@ -12,26 +13,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getAllTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getAll((err, results) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getAll();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAllTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -41,26 +27,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getSystemTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getSystemTypes((err, results) => {
|
||||
if (err) {
|
||||
console.error('시스템 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getSystemTypes();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getSystemTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('시스템 휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,26 +41,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getSpecialTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getSpecialTypes((err, results) => {
|
||||
if (err) {
|
||||
console.error('특별 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getSpecialTypes();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getSpecialTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('특별 휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,15 +55,8 @@ const vacationTypeController = {
|
||||
*/
|
||||
async createType(req, res) {
|
||||
try {
|
||||
const {
|
||||
type_code,
|
||||
type_name,
|
||||
deduct_days,
|
||||
priority,
|
||||
description
|
||||
} = req.body;
|
||||
const { type_code, type_name, deduct_days, priority, description } = req.body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!type_code || !type_name || !deduct_days) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -116,23 +65,11 @@ const vacationTypeController = {
|
||||
}
|
||||
|
||||
// type_code 중복 체크
|
||||
vacationTypeModel.getByCode(type_code, (err, existingTypes) => {
|
||||
if (err) {
|
||||
console.error('type_code 중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'type_code 중복 체크 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const existingTypes = await vacationTypeModel.getByCode(type_code);
|
||||
if (existingTypes && existingTypes.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '이미 존재하는 type_code입니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '이미 존재하는 type_code입니다' });
|
||||
}
|
||||
|
||||
// 특별 휴가 유형으로 생성
|
||||
const typeData = {
|
||||
type_code,
|
||||
type_name,
|
||||
@@ -144,28 +81,15 @@ const vacationTypeController = {
|
||||
is_active: true
|
||||
};
|
||||
|
||||
vacationTypeModel.create(typeData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 생성하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await vacationTypeModel.create(typeData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '특별 휴가 유형이 생성되었습니다',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('createType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 생성 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -176,41 +100,20 @@ const vacationTypeController = {
|
||||
async updateType(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
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 { type_name, deduct_days, priority, description, is_active } = req.body;
|
||||
|
||||
const types = await vacationTypeModel.getById(id);
|
||||
if (!types || types.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 찾을 수 없습니다'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '휴가 유형을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const type = types[0];
|
||||
|
||||
// 시스템 기본 휴가의 경우 제한적으로만 수정 가능
|
||||
const updateData = {};
|
||||
|
||||
if (type.is_system) {
|
||||
// 시스템 휴가는 priority와 description만 수정 가능
|
||||
if (priority !== undefined) updateData.priority = priority;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
} else {
|
||||
// 특별 휴가는 모든 필드 수정 가능
|
||||
if (type_name) updateData.type_name = type_name;
|
||||
if (deduct_days !== undefined) updateData.deduct_days = deduct_days;
|
||||
if (priority !== undefined) updateData.priority = priority;
|
||||
@@ -219,35 +122,15 @@ const vacationTypeController = {
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '수정할 데이터가 없습니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
updateData.updated_at = new Date();
|
||||
|
||||
vacationTypeModel.update(id, updateData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 수정 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 수정하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 유형이 수정되었습니다'
|
||||
});
|
||||
});
|
||||
});
|
||||
await vacationTypeModel.update(id, updateData);
|
||||
res.json({ success: true, message: '휴가 유형이 수정되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('updateType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 수정 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -258,15 +141,7 @@ const vacationTypeController = {
|
||||
async deleteType(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
vacationTypeModel.delete(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 삭제 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 삭제하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
const result = await vacationTypeModel.delete(id);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(400).json({
|
||||
@@ -275,17 +150,10 @@ const vacationTypeController = {
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 유형이 삭제되었습니다'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '휴가 유형이 삭제되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('deleteType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -297,35 +165,22 @@ const vacationTypeController = {
|
||||
try {
|
||||
const { priorities } = req.body;
|
||||
|
||||
// priorities = [{ id: 1, priority: 10 }, { id: 2, priority: 20 }, ...]
|
||||
if (!priorities || !Array.isArray(priorities)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'priorities 배열이 필요합니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: 'priorities 배열이 필요합니다' });
|
||||
}
|
||||
|
||||
vacationTypeModel.updatePriorities(priorities, (err, result) => {
|
||||
if (err) {
|
||||
console.error('우선순위 업데이트 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '우선순위를 업데이트하는 중 오류가 발생했습니다'
|
||||
});
|
||||
for (const { id, priority } of priorities) {
|
||||
await vacationTypeModel.updatePriority(id, priority);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '우선순위가 업데이트되었습니다',
|
||||
data: { updated: result.affectedRows }
|
||||
});
|
||||
data: { updated: priorities.length }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('updatePriorities 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('우선순위 업데이트 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,50 +1,34 @@
|
||||
const visitRequestModel = require('../models/visitRequestModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 출입 신청 관리 ====================
|
||||
|
||||
/**
|
||||
* 출입 신청 생성
|
||||
*/
|
||||
exports.createVisitRequest = (req, res) => {
|
||||
exports.createVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const requester_id = req.user.user_id;
|
||||
const requestData = {
|
||||
requester_id,
|
||||
...req.body
|
||||
};
|
||||
const requestData = { requester_id, ...req.body };
|
||||
|
||||
// 필수 필드 검증
|
||||
const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id'];
|
||||
for (const field of requiredFields) {
|
||||
if (!requestData[field]) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `${field}는 필수 입력 항목입니다.`
|
||||
});
|
||||
return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
|
||||
}
|
||||
}
|
||||
|
||||
visitRequestModel.createVisitRequest(requestData, (err, requestId) => {
|
||||
if (err) {
|
||||
console.error('출입 신청 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '출입 신청 생성 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
const requestId = await visitRequestModel.createVisitRequest(requestData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '출입 신청이 성공적으로 생성되었습니다.',
|
||||
data: { request_id: requestId }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 생성 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 생성 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 목록 조회
|
||||
*/
|
||||
exports.getAllVisitRequests = (req, res) => {
|
||||
exports.getAllVisitRequests = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
visit_date: req.query.visit_date,
|
||||
@@ -54,359 +38,168 @@ exports.getAllVisitRequests = (req, res) => {
|
||||
category_id: req.query.category_id
|
||||
};
|
||||
|
||||
visitRequestModel.getAllVisitRequests(filters, (err, requests) => {
|
||||
if (err) {
|
||||
console.error('출입 신청 목록 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '출입 신청 목록 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
const requests = await visitRequestModel.getAllVisitRequests(filters);
|
||||
res.json({ success: true, data: requests });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 목록 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: requests
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상세 조회
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.getVisitRequestById = async (req, res) => {
|
||||
try {
|
||||
const request = await visitRequestModel.getVisitRequestById(req.params.id);
|
||||
if (!request) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (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
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.updateVisitRequest(req.params.id, req.body);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (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
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.deleteVisitRequest(req.params.id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (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
|
||||
});
|
||||
}
|
||||
|
||||
exports.approveVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.approveVisitRequest(req.params.id, req.user.user_id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (req, res) => {
|
||||
const requestId = req.params.id;
|
||||
const approvedBy = req.user.user_id;
|
||||
const rejectionReason = req.body.rejection_reason || '사유 없음';
|
||||
|
||||
exports.rejectVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const rejectionData = {
|
||||
approved_by: approvedBy,
|
||||
rejection_reason: rejectionReason
|
||||
approved_by: req.user.user_id,
|
||||
rejection_reason: req.body.rejection_reason || '사유 없음'
|
||||
};
|
||||
|
||||
visitRequestModel.rejectVisitRequest(requestId, rejectionData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('출입 신청 반려 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '출입 신청 반려 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
const result = await visitRequestModel.rejectVisitRequest(req.params.id, rejectionData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (req, res) => {
|
||||
visitRequestModel.getAllVisitPurposes((err, purposes) => {
|
||||
if (err) {
|
||||
console.error('방문 목적 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '방문 목적 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
exports.getAllVisitPurposes = async (req, res) => {
|
||||
try {
|
||||
const purposes = await visitRequestModel.getAllVisitPurposes();
|
||||
res.json({ success: true, data: purposes });
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: purposes
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 방문 목적만 조회
|
||||
*/
|
||||
exports.getActiveVisitPurposes = (req, res) => {
|
||||
visitRequestModel.getActiveVisitPurposes((err, purposes) => {
|
||||
if (err) {
|
||||
console.error('활성 방문 목적 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '활성 방문 목적 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
exports.getActiveVisitPurposes = async (req, res) => {
|
||||
try {
|
||||
const purposes = await visitRequestModel.getActiveVisitPurposes();
|
||||
res.json({ success: true, data: purposes });
|
||||
} catch (err) {
|
||||
logger.error('활성 방문 목적 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '활성 방문 목적 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: purposes
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 추가
|
||||
*/
|
||||
exports.createVisitPurpose = (req, res) => {
|
||||
const purposeData = req.body;
|
||||
|
||||
if (!purposeData.purpose_name) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'purpose_name은 필수 입력 항목입니다.'
|
||||
});
|
||||
exports.createVisitPurpose = async (req, res) => {
|
||||
try {
|
||||
if (!req.body.purpose_name) {
|
||||
return res.status(400).json({ success: false, message: 'purpose_name은 필수 입력 항목입니다.' });
|
||||
}
|
||||
|
||||
visitRequestModel.createVisitPurpose(purposeData, (err, purposeId) => {
|
||||
if (err) {
|
||||
console.error('방문 목적 추가 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '방문 목적 추가 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
const purposeId = await visitRequestModel.createVisitPurpose(req.body);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '방문 목적이 추가되었습니다.',
|
||||
data: { purpose_id: purposeId }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 추가 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 추가 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 수정
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateVisitPurpose = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.updateVisitPurpose(req.params.id, req.body);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '방문 목적을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (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
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteVisitPurpose = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.deleteVisitPurpose(req.params.id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '방문 목적을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (req, res) => {
|
||||
const trainerId = req.user.user_id;
|
||||
const trainingData = {
|
||||
trainer_id: trainerId,
|
||||
...req.body
|
||||
};
|
||||
exports.createTrainingRecord = async (req, res) => {
|
||||
try {
|
||||
const trainingData = { trainer_id: req.user.user_id, ...req.body };
|
||||
|
||||
// 필수 필드 검증
|
||||
const requiredFields = ['request_id', 'training_date', 'training_start_time'];
|
||||
for (const field of requiredFields) {
|
||||
if (!trainingData[field]) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `${field}는 필수 입력 항목입니다.`
|
||||
});
|
||||
return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
|
||||
}
|
||||
}
|
||||
|
||||
visitRequestModel.createTrainingRecord(trainingData, (err, trainingId) => {
|
||||
if (err) {
|
||||
console.error('안전교육 기록 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '안전교육 기록 생성 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
const trainingId = await visitRequestModel.createTrainingRecord(trainingData);
|
||||
|
||||
// 안전교육 기록이 생성되면 출입 신청 상태를 training_completed로 변경
|
||||
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태를 training_completed로 변경 중...`);
|
||||
visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed', (statusErr) => {
|
||||
if (statusErr) {
|
||||
console.error('출입 신청 상태 업데이트 오류:', statusErr);
|
||||
// 에러가 발생해도 교육 기록은 생성되었으므로 성공 응답
|
||||
} else {
|
||||
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태 변경 성공`);
|
||||
// 안전교육 기록 생성 후 출입 신청 상태를 training_completed로 변경
|
||||
try {
|
||||
await visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed');
|
||||
} catch (statusErr) {
|
||||
logger.error('출입 신청 상태 업데이트 오류:', statusErr);
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
@@ -414,142 +207,78 @@ exports.createTrainingRecord = (req, res) => {
|
||||
message: '안전교육 기록이 생성되었습니다.',
|
||||
data: { training_id: trainingId }
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('안전교육 기록 생성 오류:', err);
|
||||
res.status(500).json({ success: false, message: '안전교육 기록 생성 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 출입 신청의 안전교육 기록 조회
|
||||
*/
|
||||
exports.getTrainingRecordByRequestId = (req, res) => {
|
||||
const requestId = req.params.requestId;
|
||||
|
||||
visitRequestModel.getTrainingRecordByRequestId(requestId, (err, record) => {
|
||||
if (err) {
|
||||
console.error('안전교육 기록 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '안전교육 기록 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
exports.getTrainingRecordByRequestId = async (req, res) => {
|
||||
try {
|
||||
const record = await visitRequestModel.getTrainingRecordByRequestId(req.params.requestId);
|
||||
res.json({ success: true, data: record || null });
|
||||
} catch (err) {
|
||||
logger.error('안전교육 기록 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '안전교육 기록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: record || null
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 기록 수정
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateTrainingRecord = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.updateTrainingRecord(req.params.id, req.body);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '안전교육 기록을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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 = (req, res) => {
|
||||
exports.completeTraining = async (req, res) => {
|
||||
try {
|
||||
const trainingId = req.params.id;
|
||||
const signatureData = req.body.signature_data;
|
||||
|
||||
if (!signatureData) {
|
||||
return res.status(400).json({
|
||||
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
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '서명 데이터가 필요합니다.' });
|
||||
}
|
||||
|
||||
const result = await visitRequestModel.completeTraining(trainingId, signatureData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '안전교육 기록을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 교육 완료 후 출입 신청 상태를 'training_completed'로 변경
|
||||
visitRequestModel.getTrainingRecordByRequestId(trainingId, (err, record) => {
|
||||
if (err || !record) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '안전교육이 완료되었습니다.'
|
||||
});
|
||||
// 교육 완료 후 출입 신청 상태 변경
|
||||
try {
|
||||
const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId);
|
||||
if (record) {
|
||||
await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed');
|
||||
}
|
||||
} catch (statusErr) {
|
||||
logger.error('출입 신청 상태 업데이트 오류:', statusErr);
|
||||
}
|
||||
|
||||
visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed', (err) => {
|
||||
if (err) {
|
||||
console.error('출입 신청 상태 업데이트 오류:', err);
|
||||
res.json({ success: true, message: '안전교육이 완료되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('안전교육 완료 처리 오류:', err);
|
||||
res.status(500).json({ success: false, message: '안전교육 완료 처리 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '안전교육이 완료되었습니다.'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 기록 목록 조회
|
||||
*/
|
||||
exports.getTrainingRecords = (req, res) => {
|
||||
exports.getTrainingRecords = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
training_date: req.query.training_date,
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
trainer_id: req.query.trainer_id
|
||||
};
|
||||
|
||||
visitRequestModel.getTrainingRecords(filters, (err, records) => {
|
||||
if (err) {
|
||||
console.error('안전교육 기록 목록 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
const records = await visitRequestModel.getTrainingRecords(filters);
|
||||
res.json({ success: true, data: records });
|
||||
} catch (err) {
|
||||
logger.error('안전교육 기록 목록 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: records
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,198 +4,149 @@
|
||||
|
||||
const workIssueModel = require('../models/workIssueModel');
|
||||
const imageUploadService = require('../services/imageUploadService');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 신고 카테고리 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 카테고리 조회
|
||||
*/
|
||||
exports.getAllCategories = (req, res) => {
|
||||
workIssueModel.getAllCategories((err, categories) => {
|
||||
if (err) {
|
||||
console.error('카테고리 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
exports.getAllCategories = async (req, res) => {
|
||||
try {
|
||||
const categories = await workIssueModel.getAllCategories();
|
||||
res.json({ success: true, data: categories });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 타입별 카테고리 조회
|
||||
*/
|
||||
exports.getCategoriesByType = (req, res) => {
|
||||
exports.getCategoriesByType = async (req, res) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
|
||||
if (!['nonconformity', 'safety', 'facility'].includes(type)) {
|
||||
return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.getCategoriesByType(type, (err, categories) => {
|
||||
if (err) {
|
||||
console.error('카테고리 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
const categories = await workIssueModel.getCategoriesByType(type);
|
||||
res.json({ success: true, data: categories });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
exports.createCategory = (req, res) => {
|
||||
exports.createCategory = async (req, res) => {
|
||||
try {
|
||||
const { category_type, category_name, description, display_order } = req.body;
|
||||
|
||||
if (!category_type || !category_name) {
|
||||
return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.createCategory(
|
||||
{ category_type, category_name, description, display_order },
|
||||
(err, categoryId) => {
|
||||
if (err) {
|
||||
console.error('카테고리 생성 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 생성 실패' });
|
||||
}
|
||||
const categoryId = await workIssueModel.createCategory({ category_type, category_name, description, display_order });
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '카테고리가 생성되었습니다.',
|
||||
data: { category_id: categoryId }
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 생성 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 생성 실패' });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
exports.updateCategory = (req, res) => {
|
||||
exports.updateCategory = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { category_name, description, display_order, is_active } = req.body;
|
||||
|
||||
workIssueModel.updateCategory(
|
||||
id,
|
||||
{ category_name, description, display_order, is_active },
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
console.error('카테고리 수정 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 수정 실패' });
|
||||
}
|
||||
await workIssueModel.updateCategory(id, { category_name, description, display_order, is_active });
|
||||
res.json({ success: true, message: '카테고리가 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('카테고리 수정 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 수정 실패' });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
exports.deleteCategory = (req, res) => {
|
||||
exports.deleteCategory = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.deleteCategory(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('카테고리 삭제 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
|
||||
}
|
||||
await workIssueModel.deleteCategory(id);
|
||||
res.json({ success: true, message: '카테고리가 삭제되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 삭제 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 사전 정의 항목 관리 ====================
|
||||
|
||||
/**
|
||||
* 카테고리별 항목 조회
|
||||
*/
|
||||
exports.getItemsByCategory = (req, res) => {
|
||||
exports.getItemsByCategory = async (req, res) => {
|
||||
try {
|
||||
const { categoryId } = req.params;
|
||||
|
||||
workIssueModel.getItemsByCategory(categoryId, (err, items) => {
|
||||
if (err) {
|
||||
console.error('항목 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
const items = await workIssueModel.getItemsByCategory(categoryId);
|
||||
res.json({ success: true, data: items });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('항목 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 항목 조회
|
||||
*/
|
||||
exports.getAllItems = (req, res) => {
|
||||
workIssueModel.getAllItems((err, items) => {
|
||||
if (err) {
|
||||
console.error('항목 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
exports.getAllItems = async (req, res) => {
|
||||
try {
|
||||
const items = await workIssueModel.getAllItems();
|
||||
res.json({ success: true, data: items });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('항목 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 생성
|
||||
*/
|
||||
exports.createItem = (req, res) => {
|
||||
exports.createItem = async (req, res) => {
|
||||
try {
|
||||
const { category_id, item_name, description, severity, display_order } = req.body;
|
||||
|
||||
if (!category_id || !item_name) {
|
||||
return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.createItem(
|
||||
{ category_id, item_name, description, severity, display_order },
|
||||
(err, itemId) => {
|
||||
if (err) {
|
||||
console.error('항목 생성 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 생성 실패' });
|
||||
}
|
||||
const itemId = await workIssueModel.createItem({ category_id, item_name, description, severity, display_order });
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '항목이 생성되었습니다.',
|
||||
data: { item_id: itemId }
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('항목 생성 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 생성 실패' });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 수정
|
||||
*/
|
||||
exports.updateItem = (req, res) => {
|
||||
exports.updateItem = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { item_name, description, severity, display_order, is_active } = req.body;
|
||||
|
||||
workIssueModel.updateItem(
|
||||
id,
|
||||
{ item_name, description, severity, display_order, is_active },
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
console.error('항목 수정 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 수정 실패' });
|
||||
}
|
||||
await workIssueModel.updateItem(id, { item_name, description, severity, display_order, is_active });
|
||||
res.json({ success: true, message: '항목이 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('항목 수정 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 수정 실패' });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 삭제
|
||||
*/
|
||||
exports.deleteItem = (req, res) => {
|
||||
exports.deleteItem = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.deleteItem(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('항목 삭제 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 삭제 실패' });
|
||||
}
|
||||
await workIssueModel.deleteItem(id);
|
||||
res.json({ success: true, message: '항목이 삭제되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('항목 삭제 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 삭제 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 문제 신고 관리 ====================
|
||||
|
||||
/**
|
||||
* 신고 생성
|
||||
*/
|
||||
exports.createReport = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
@@ -206,7 +157,7 @@ exports.createReport = async (req, res) => {
|
||||
visit_request_id,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
custom_item_name, // 직접 입력한 항목명
|
||||
custom_item_name,
|
||||
additional_description,
|
||||
photos = []
|
||||
} = req.body;
|
||||
@@ -217,42 +168,25 @@ exports.createReport = async (req, res) => {
|
||||
return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' });
|
||||
}
|
||||
|
||||
// 위치 정보 검증 (지도 선택 또는 기타 위치)
|
||||
if (!factory_category_id && !custom_location) {
|
||||
return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' });
|
||||
}
|
||||
|
||||
// 항목 검증 (기존 항목 또는 직접 입력)
|
||||
if (!issue_item_id && !custom_item_name) {
|
||||
return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' });
|
||||
}
|
||||
|
||||
// 직접 입력한 항목이 있으면 DB에 저장
|
||||
let finalItemId = issue_item_id;
|
||||
if (custom_item_name && !issue_item_id) {
|
||||
try {
|
||||
finalItemId = await new Promise((resolve, reject) => {
|
||||
workIssueModel.createItem(
|
||||
{
|
||||
finalItemId = await workIssueModel.createItem({
|
||||
category_id: issue_category_id,
|
||||
item_name: custom_item_name,
|
||||
description: '사용자 직접 입력',
|
||||
severity: 'medium',
|
||||
display_order: 999 // 마지막에 표시
|
||||
},
|
||||
(err, itemId) => {
|
||||
if (err) reject(err);
|
||||
else resolve(itemId);
|
||||
}
|
||||
);
|
||||
display_order: 999
|
||||
});
|
||||
} catch (itemErr) {
|
||||
console.error('커스텀 항목 생성 실패:', itemErr);
|
||||
return res.status(500).json({ success: false, error: '항목 저장 실패' });
|
||||
}
|
||||
}
|
||||
|
||||
// 사진 저장 (최대 5장)
|
||||
const photoPaths = {
|
||||
photo_path1: null,
|
||||
photo_path2: null,
|
||||
@@ -283,27 +217,20 @@ exports.createReport = async (req, res) => {
|
||||
...photoPaths
|
||||
};
|
||||
|
||||
workIssueModel.createReport(reportData, (err, reportId) => {
|
||||
if (err) {
|
||||
console.error('신고 생성 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 생성 실패' });
|
||||
}
|
||||
const reportId = await workIssueModel.createReport(reportData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '문제 신고가 등록되었습니다.',
|
||||
data: { report_id: reportId }
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('신고 생성 에러:', error);
|
||||
} catch (err) {
|
||||
logger.error('신고 생성 실패:', err);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 목록 조회
|
||||
*/
|
||||
exports.getAllReports = (req, res) => {
|
||||
exports.getAllReports = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
category_type: req.query.category_type,
|
||||
@@ -318,38 +245,28 @@ exports.getAllReports = (req, res) => {
|
||||
offset: req.query.offset
|
||||
};
|
||||
|
||||
// 일반 사용자는 자신의 신고만 조회 (관리자 제외)
|
||||
const userLevel = req.user.access_level;
|
||||
if (!['admin', 'system', 'support_team'].includes(userLevel)) {
|
||||
filters.reporter_id = req.user.user_id;
|
||||
}
|
||||
|
||||
workIssueModel.getAllReports(filters, (err, reports) => {
|
||||
if (err) {
|
||||
console.error('신고 목록 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
|
||||
}
|
||||
const reports = await workIssueModel.getAllReports(filters);
|
||||
res.json({ success: true, data: reports });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 목록 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 상세 조회
|
||||
*/
|
||||
exports.getReportById = (req, res) => {
|
||||
exports.getReportById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.getReportById(id, (err, report) => {
|
||||
if (err) {
|
||||
console.error('신고 상세 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
|
||||
}
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인: 본인, 담당자, 또는 관리자
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_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 });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 상세 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 수정
|
||||
*/
|
||||
exports.updateReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 기존 신고 확인
|
||||
workIssueModel.getReportById(id, async (err, report) => {
|
||||
if (err) {
|
||||
console.error('신고 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 조회 실패' });
|
||||
}
|
||||
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_id === req.user.user_id;
|
||||
const isManager = ['admin', 'system'].includes(userLevel);
|
||||
@@ -390,7 +300,6 @@ exports.updateReport = async (req, res) => {
|
||||
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' });
|
||||
}
|
||||
|
||||
// 상태 확인: reported 상태에서만 수정 가능 (관리자 제외)
|
||||
if (!isManager && report.status !== 'reported') {
|
||||
return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' });
|
||||
}
|
||||
@@ -405,16 +314,13 @@ exports.updateReport = async (req, res) => {
|
||||
photos = []
|
||||
} = req.body;
|
||||
|
||||
// 사진 업데이트 처리
|
||||
const photoPaths = {};
|
||||
for (let i = 0; i < Math.min(photos.length, 5); i++) {
|
||||
if (photos[i]) {
|
||||
// 기존 사진 삭제
|
||||
const oldPath = report[`photo_path${i + 1}`];
|
||||
if (oldPath) {
|
||||
await imageUploadService.deleteFile(oldPath);
|
||||
}
|
||||
// 새 사진 저장
|
||||
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue');
|
||||
if (savedPath) {
|
||||
photoPaths[`photo_path${i + 1}`] = savedPath;
|
||||
@@ -432,37 +338,23 @@ exports.updateReport = async (req, res) => {
|
||||
...photoPaths
|
||||
};
|
||||
|
||||
workIssueModel.updateReport(id, updateData, req.user.user_id, (updateErr, result) => {
|
||||
if (updateErr) {
|
||||
console.error('신고 수정 실패:', updateErr);
|
||||
return res.status(500).json({ success: false, error: '신고 수정 실패' });
|
||||
}
|
||||
await workIssueModel.updateReport(id, updateData, req.user.user_id);
|
||||
res.json({ success: true, message: '신고가 수정되었습니다.' });
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('신고 수정 에러:', error);
|
||||
} catch (err) {
|
||||
logger.error('신고 수정 실패:', err);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 삭제
|
||||
*/
|
||||
exports.deleteReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.getReportById(id, async (err, report) => {
|
||||
if (err) {
|
||||
console.error('신고 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 조회 실패' });
|
||||
}
|
||||
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_id === req.user.user_id;
|
||||
const isManager = ['admin', 'system'].includes(userLevel);
|
||||
@@ -471,13 +363,8 @@ exports.deleteReport = async (req, res) => {
|
||||
return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.deleteReport(id, async (deleteErr, { result, photos }) => {
|
||||
if (deleteErr) {
|
||||
console.error('신고 삭제 실패:', deleteErr);
|
||||
return res.status(500).json({ success: false, error: '신고 삭제 실패' });
|
||||
}
|
||||
const { photos } = await workIssueModel.deleteReport(id);
|
||||
|
||||
// 사진 파일 삭제
|
||||
if (photos) {
|
||||
const allPhotos = [
|
||||
photos.photo_path1, photos.photo_path2, photos.photo_path3,
|
||||
@@ -488,31 +375,27 @@ exports.deleteReport = async (req, res) => {
|
||||
}
|
||||
|
||||
res.json({ success: true, message: '신고가 삭제되었습니다.' });
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 삭제 실패:', err);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 상태 관리 ====================
|
||||
|
||||
/**
|
||||
* 신고 접수
|
||||
*/
|
||||
exports.receiveReport = (req, res) => {
|
||||
exports.receiveReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.receiveReport(id, req.user.user_id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('신고 접수 실패:', err);
|
||||
return res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
|
||||
}
|
||||
await workIssueModel.receiveReport(id, req.user.user_id);
|
||||
res.json({ success: true, message: '신고가 접수되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 접수 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 담당자 배정
|
||||
*/
|
||||
exports.assignReport = (req, res) => {
|
||||
exports.assignReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { assigned_department, assigned_user_id } = req.body;
|
||||
|
||||
@@ -520,43 +403,34 @@ exports.assignReport = (req, res) => {
|
||||
return res.status(400).json({ success: false, error: '담당자는 필수입니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.assignReport(id, {
|
||||
await workIssueModel.assignReport(id, {
|
||||
assigned_department,
|
||||
assigned_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: '담당자가 배정되었습니다.' });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 시작
|
||||
*/
|
||||
exports.startProcessing = (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.startProcessing(id, req.user.user_id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('처리 시작 실패:', err);
|
||||
return res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
|
||||
} catch (err) {
|
||||
logger.error('담당자 배정 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.startProcessing = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await workIssueModel.startProcessing(id, req.user.user_id);
|
||||
res.json({ success: true, message: '처리가 시작되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('처리 시작 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
|
||||
}
|
||||
res.json({ success: true, message: '처리가 시작되었습니다.' });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 완료
|
||||
*/
|
||||
exports.completeReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { resolution_notes, resolution_photos = [] } = req.body;
|
||||
|
||||
// 완료 사진 저장
|
||||
let resolution_photo_path1 = 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');
|
||||
}
|
||||
|
||||
workIssueModel.completeReport(id, {
|
||||
await workIssueModel.completeReport(id, {
|
||||
resolution_notes,
|
||||
resolution_photo_path1,
|
||||
resolution_photo_path2,
|
||||
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: '처리가 완료되었습니다.' });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('처리 완료 에러:', error);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('처리 완료 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 종료
|
||||
*/
|
||||
exports.closeReport = (req, res) => {
|
||||
exports.closeReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.closeReport(id, req.user.user_id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('신고 종료 실패:', err);
|
||||
return res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
|
||||
}
|
||||
await workIssueModel.closeReport(id, req.user.user_id);
|
||||
res.json({ success: true, message: '신고가 종료되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 종료 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 상태 변경 이력 조회
|
||||
*/
|
||||
exports.getStatusLogs = (req, res) => {
|
||||
exports.getStatusLogs = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.getStatusLogs(id, (err, logs) => {
|
||||
if (err) {
|
||||
console.error('상태 이력 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
|
||||
}
|
||||
const logs = await workIssueModel.getStatusLogs(id);
|
||||
res.json({ success: true, data: logs });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('상태 이력 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 통계 ====================
|
||||
|
||||
/**
|
||||
* 통계 요약
|
||||
*/
|
||||
exports.getStatsSummary = (req, res) => {
|
||||
exports.getStatsSummary = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
|
||||
workIssueModel.getStatsSummary(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
const stats = await workIssueModel.getStatsSummary(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리별 통계
|
||||
*/
|
||||
exports.getStatsByCategory = (req, res) => {
|
||||
exports.getStatsByCategory = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date
|
||||
};
|
||||
|
||||
workIssueModel.getStatsByCategory(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('카테고리별 통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
const stats = await workIssueModel.getStatsByCategory(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리별 통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장별 통계
|
||||
*/
|
||||
exports.getStatsByWorkplace = (req, res) => {
|
||||
exports.getStatsByWorkplace = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
|
||||
workIssueModel.getStatsByWorkplace(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('작업장별 통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
const stats = await workIssueModel.getStatsByWorkplace(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('작업장별 통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,15 +8,12 @@
|
||||
*/
|
||||
|
||||
const workplaceModel = require('../models/workplaceModel');
|
||||
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
|
||||
const { ValidationError, NotFoundError } = require('../utils/errors');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 카테고리(공장) 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
exports.createCategory = asyncHandler(async (req, res) => {
|
||||
const categoryData = req.body;
|
||||
|
||||
@@ -26,12 +23,7 @@ exports.createCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 생성 요청', { name: categoryData.category_name });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createCategory(categoryData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createCategory(categoryData);
|
||||
|
||||
logger.info('카테고리 생성 성공', { category_id: id });
|
||||
|
||||
@@ -42,16 +34,8 @@ exports.createCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 전체 카테고리 조회
|
||||
*/
|
||||
exports.getAllCategories = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getAllCategories((err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getAllCategories();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -60,16 +44,8 @@ exports.getAllCategories = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 활성 카테고리만 조회
|
||||
*/
|
||||
exports.getActiveCategories = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getActiveCategories((err, data) => {
|
||||
if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getActiveCategories();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -78,18 +54,9 @@ exports.getActiveCategories = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 단일 카테고리 조회
|
||||
*/
|
||||
exports.getCategoryById = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
const category = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const category = await workplaceModel.getCategoryById(categoryId);
|
||||
|
||||
if (!category) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
@@ -102,9 +69,6 @@ exports.getCategoryById = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
const categoryData = req.body;
|
||||
@@ -115,19 +79,11 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 수정 요청', { category_id: categoryId });
|
||||
|
||||
// 기존 카테고리 정보 가져오기
|
||||
const existingCategory = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const existingCategory = await workplaceModel.getCategoryById(categoryId);
|
||||
if (!existingCategory) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// layout_image가 요청에 없거나 null이면 기존 값 보존
|
||||
const updateData = {
|
||||
...categoryData,
|
||||
layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null)
|
||||
@@ -135,12 +91,7 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
: existingCategory.layout_image
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateCategory(categoryId, updateData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateCategory(categoryId, updateData);
|
||||
|
||||
logger.info('카테고리 수정 성공', { category_id: categoryId });
|
||||
|
||||
@@ -150,20 +101,12 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
exports.deleteCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
logger.info('카테고리 삭제 요청', { category_id: categoryId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteCategory(categoryId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteCategory(categoryId);
|
||||
|
||||
logger.info('카테고리 삭제 성공', { category_id: categoryId });
|
||||
|
||||
@@ -175,9 +118,6 @@ exports.deleteCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
// ==================== 작업장 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 생성
|
||||
*/
|
||||
exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceData = req.body;
|
||||
|
||||
@@ -187,12 +127,7 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 생성 요청', { name: workplaceData.workplace_name });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createWorkplace(workplaceData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createWorkplace(workplaceData);
|
||||
|
||||
logger.info('작업장 생성 성공', { workplace_id: id });
|
||||
|
||||
@@ -203,35 +138,12 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 전체 작업장 조회
|
||||
*/
|
||||
exports.getAllWorkplaces = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.query.category_id;
|
||||
|
||||
// 카테고리별 필터링
|
||||
if (categoryId) {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
const rows = categoryId
|
||||
? await workplaceModel.getWorkplacesByCategory(categoryId)
|
||||
: await workplaceModel.getAllWorkplaces();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -240,16 +152,8 @@ exports.getAllWorkplaces = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 활성 작업장만 조회
|
||||
*/
|
||||
exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getActiveWorkplaces((err, data) => {
|
||||
if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getActiveWorkplaces();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -258,18 +162,9 @@ exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 단일 작업장 조회
|
||||
*/
|
||||
exports.getWorkplaceById = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
const workplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
|
||||
if (!workplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
@@ -282,9 +177,6 @@ exports.getWorkplaceById = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 수정
|
||||
*/
|
||||
exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
const workplaceData = req.body;
|
||||
@@ -295,19 +187,11 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 수정 요청', { workplace_id: workplaceId });
|
||||
|
||||
// 기존 작업장 정보 가져오기
|
||||
const existingWorkplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const existingWorkplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
if (!existingWorkplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// layout_image가 요청에 없거나 null이면 기존 값 보존
|
||||
const updateData = {
|
||||
...workplaceData,
|
||||
layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null)
|
||||
@@ -315,12 +199,7 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
: existingWorkplace.layout_image
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateWorkplace(workplaceId, updateData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateWorkplace(workplaceId, updateData);
|
||||
|
||||
logger.info('작업장 수정 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -330,20 +209,12 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 삭제
|
||||
*/
|
||||
exports.deleteWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
logger.info('작업장 삭제 요청', { workplace_id: workplaceId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteWorkplace(workplaceId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteWorkplace(workplaceId);
|
||||
|
||||
logger.info('작업장 삭제 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -355,9 +226,6 @@ exports.deleteWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
// ==================== 작업장 지도 영역 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 레이아웃 이미지 업로드
|
||||
*/
|
||||
exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
@@ -369,19 +237,11 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath });
|
||||
|
||||
// 현재 카테고리 정보 가져오기
|
||||
const category = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const category = await workplaceModel.getCategoryById(categoryId);
|
||||
if (!category) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 카테고리 정보 업데이트 (이미지 경로만 변경)
|
||||
const updatedData = {
|
||||
category_name: category.category_name,
|
||||
description: category.description,
|
||||
@@ -390,12 +250,7 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
layout_image: imagePath
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateCategory(categoryId, updatedData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateCategory(categoryId, updatedData);
|
||||
|
||||
logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId });
|
||||
|
||||
@@ -406,9 +261,6 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 레이아웃 이미지 업로드
|
||||
*/
|
||||
exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
@@ -420,19 +272,11 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath });
|
||||
|
||||
// 현재 작업장 정보 가져오기
|
||||
const workplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
if (!workplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 작업장 정보 업데이트 (이미지 경로만 변경)
|
||||
const updatedData = {
|
||||
workplace_name: workplace.workplace_name,
|
||||
category_id: workplace.category_id,
|
||||
@@ -443,12 +287,7 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
layout_image: imagePath
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateWorkplace(workplaceId, updatedData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateWorkplace(workplaceId, updatedData);
|
||||
|
||||
logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -459,9 +298,6 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 생성
|
||||
*/
|
||||
exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionData = req.body;
|
||||
|
||||
@@ -471,12 +307,7 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createMapRegion(regionData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createMapRegion(regionData);
|
||||
|
||||
logger.info('지도 영역 생성 성공', { region_id: id });
|
||||
|
||||
@@ -487,18 +318,9 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리별 지도 영역 조회 (작업장 정보 포함)
|
||||
*/
|
||||
exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.categoryId;
|
||||
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getMapRegionsByCategory(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getMapRegionsByCategory(categoryId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -507,18 +329,9 @@ exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장별 지도 영역 조회
|
||||
*/
|
||||
exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.workplaceId;
|
||||
|
||||
const region = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getMapRegionByWorkplace(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const region = await workplaceModel.getMapRegionByWorkplace(workplaceId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -527,21 +340,13 @@ exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 수정
|
||||
*/
|
||||
exports.updateMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionId = req.params.id;
|
||||
const regionData = req.body;
|
||||
|
||||
logger.info('지도 영역 수정 요청', { region_id: regionId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateMapRegion(regionId, regionData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateMapRegion(regionId, regionData);
|
||||
|
||||
logger.info('지도 영역 수정 성공', { region_id: regionId });
|
||||
|
||||
@@ -551,20 +356,12 @@ exports.updateMapRegion = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 삭제
|
||||
*/
|
||||
exports.deleteMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionId = req.params.id;
|
||||
|
||||
logger.info('지도 영역 삭제 요청', { region_id: regionId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteMapRegion(regionId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteMapRegion(regionId);
|
||||
|
||||
logger.info('지도 영역 삭제 성공', { region_id: regionId });
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
/**
|
||||
* 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다.
|
||||
* @param {Array<object>} reports - 생성할 보고서 데이터 배열
|
||||
* @returns {Promise<Array<number>>} - 삽입된 ID 배열
|
||||
*/
|
||||
const createMany = async (reports) => {
|
||||
const db = await getDb();
|
||||
@@ -36,10 +34,9 @@ const createMany = async (reports) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반)
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회
|
||||
*/
|
||||
const getAllByDate = async (date) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
@@ -54,30 +51,21 @@ const getAllByDate = async (date) => {
|
||||
[date]
|
||||
);
|
||||
return rows;
|
||||
} catch (err) {
|
||||
console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err);
|
||||
throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. 단일 조회 (선택사항: 컨트롤러에서 사용 중)
|
||||
* 3. 단일 조회
|
||||
*/
|
||||
const getById = async (id, callback) => {
|
||||
try {
|
||||
const getById = async (id) => {
|
||||
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]);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 수정
|
||||
*/
|
||||
const update = async (id, data, callback) => {
|
||||
try {
|
||||
const update = async (id, data) => {
|
||||
const db = await getDb();
|
||||
|
||||
const fields = [];
|
||||
@@ -88,67 +76,29 @@ const update = async (id, data, callback) => {
|
||||
values.push(data[key]);
|
||||
}
|
||||
|
||||
values.push(id); // 마지막에 id
|
||||
values.push(id);
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`,
|
||||
values
|
||||
);
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. 삭제 (Promise 기반)
|
||||
* 5. 삭제
|
||||
*/
|
||||
const remove = async (id) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
|
||||
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 = {
|
||||
createMany, // 신규
|
||||
createMany,
|
||||
getAllByDate,
|
||||
remove,
|
||||
// 레거시 호환성을 위해 V1 함수들 임시 유지
|
||||
create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)),
|
||||
getById,
|
||||
update,
|
||||
remove
|
||||
};
|
||||
@@ -2,34 +2,22 @@
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
/**
|
||||
* 📋 마스터 데이터 조회 함수들
|
||||
* 마스터 데이터 조회 함수들
|
||||
*/
|
||||
const getAllWorkTypes = async (callback) => {
|
||||
try {
|
||||
const getAllWorkTypes = async () => {
|
||||
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');
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('작업 유형 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
const getAllWorkStatusTypes = async (callback) => {
|
||||
try {
|
||||
const getAllWorkStatusTypes = async () => {
|
||||
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');
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('업무 상태 유형 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
const getAllErrorTypes = async (callback) => {
|
||||
try {
|
||||
const getAllErrorTypes = async () => {
|
||||
const db = await getDb();
|
||||
// issue_report_items에서 부적합(nonconformity) 타입의 항목만 조회
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
iri.item_id as id,
|
||||
@@ -44,17 +32,13 @@ const getAllErrorTypes = async (callback) => {
|
||||
WHERE irc.category_type = 'nonconformity' AND iri.is_active = TRUE
|
||||
ORDER BY irc.display_order, iri.display_order, iri.item_name ASC
|
||||
`);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('에러 유형 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 🔄 누적 추가 전용 함수 (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 db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
@@ -62,9 +46,8 @@ const createDailyReport = async (reportData, callback) => {
|
||||
try {
|
||||
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(
|
||||
`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
|
||||
@@ -74,10 +57,9 @@ const createDailyReport = async (reportData, callback) => {
|
||||
[report_date, worker_id]
|
||||
);
|
||||
|
||||
|
||||
console.log('기존 데이터 (삭제하지 않음):', existingReports);
|
||||
|
||||
// 2. ✅ 삭제 없이 새로운 데이터만 추가!
|
||||
// 삭제 없이 새로운 데이터만 추가!
|
||||
const insertedIds = [];
|
||||
for (const entry of work_entries) {
|
||||
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);
|
||||
}
|
||||
|
||||
// ✅ 수정된 쿼리:
|
||||
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
|
||||
FROM daily_work_reports dwr
|
||||
@@ -109,9 +90,9 @@ const createDailyReport = async (reportData, callback) => {
|
||||
finalReports.forEach(report => {
|
||||
console.log(` - ${report.created_by_name}: ${report.total_hours}시간 (${report.count}개 항목)`);
|
||||
});
|
||||
console.log(` 📊 총합: ${grandTotal}시간`);
|
||||
console.log(` 총합: ${grandTotal}시간`);
|
||||
|
||||
// 4. 감사 로그 추가
|
||||
// 감사 로그 추가
|
||||
try {
|
||||
await conn.query(
|
||||
`INSERT INTO work_report_audit_log
|
||||
@@ -139,16 +120,15 @@ const createDailyReport = async (reportData, callback) => {
|
||||
|
||||
await conn.commit();
|
||||
|
||||
// 5. 근태 기록 동기화 (추가)
|
||||
// 근태 기록 동기화
|
||||
try {
|
||||
const AttendanceModel = require('./attendanceModel');
|
||||
await AttendanceModel.syncWithWorkReports(worker_id, report_date);
|
||||
} catch (syncErr) {
|
||||
console.error('근태 기록 동기화 실패:', syncErr);
|
||||
// 메인 트랜잭션은 성공했으므로 동기화 실패로 롤백하지 않음 (비동기 처리 또는 무시)
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
return {
|
||||
success: true,
|
||||
inserted_count: insertedIds.length,
|
||||
deleted_count: 0, // 항상 0 (삭제 안함)
|
||||
@@ -160,22 +140,20 @@ const createDailyReport = async (reportData, callback) => {
|
||||
total_contributors: finalReports.length,
|
||||
contributors: finalReports
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('작업보고서 누적 추가 오류:', err);
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 특정 날짜 + 작업자 + 작성자의 누적 현황 조회
|
||||
* 특정 날짜 + 작업자 + 작성자의 누적 현황 조회
|
||||
*/
|
||||
const getMyAccumulatedHours = async (date, worker_id, created_by, callback) => {
|
||||
try {
|
||||
const getMyAccumulatedHours = async (date, worker_id, created_by) => {
|
||||
const db = await getDb();
|
||||
|
||||
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]);
|
||||
callback(null, rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null });
|
||||
} catch (err) {
|
||||
console.error('개인 누적 현황 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null };
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 누적 현황 조회 - 날짜+작업자별 (모든 기여자)
|
||||
* 누적 현황 조회 - 날짜+작업자별 (모든 기여자)
|
||||
*/
|
||||
const getAccumulatedReportsByDate = async (date, worker_id, callback) => {
|
||||
try {
|
||||
const getAccumulatedReportsByDate = async (date, worker_id) => {
|
||||
const db = await getDb();
|
||||
|
||||
const sql = getSelectQuery() + `
|
||||
@@ -212,18 +185,13 @@ const getAccumulatedReportsByDate = async (date, worker_id, callback) => {
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [date, worker_id]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('누적 현황 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 기여자별 요약 조회
|
||||
* 기여자별 요약 조회
|
||||
*/
|
||||
const getContributorsByDate = async (date, worker_id, callback) => {
|
||||
try {
|
||||
const getContributorsByDate = async (date, worker_id) => {
|
||||
const db = await getDb();
|
||||
|
||||
const sql = `
|
||||
@@ -247,17 +215,13 @@ const getContributorsByDate = async (date, worker_id, callback) => {
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [date, worker_id]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('기여자별 요약 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 🗑️ 특정 작업 항목만 삭제 (개별 삭제)
|
||||
* 특정 작업 항목만 삭제 (개별 삭제)
|
||||
*/
|
||||
const removeSpecificEntry = async (entry_id, deleted_by, callback) => {
|
||||
const removeSpecificEntry = async (entry_id, deleted_by) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -276,16 +240,14 @@ const removeSpecificEntry = async (entry_id, deleted_by, callback) => {
|
||||
);
|
||||
|
||||
if (entryInfo.length === 0) {
|
||||
await conn.rollback();
|
||||
return callback(new Error('삭제할 항목을 찾을 수 없습니다.'));
|
||||
throw new Error('삭제할 항목을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const entry = entryInfo[0];
|
||||
|
||||
// 권한 확인: 본인이 작성한 것만 삭제 가능
|
||||
if (entry.created_by !== deleted_by) {
|
||||
await conn.rollback();
|
||||
return callback(new Error('본인이 작성한 항목만 삭제할 수 있습니다.'));
|
||||
throw 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}`);
|
||||
|
||||
await conn.commit();
|
||||
callback(null, {
|
||||
return {
|
||||
success: true,
|
||||
deleted_entry: {
|
||||
worker_name: entry.worker_name,
|
||||
project_name: entry.project_name,
|
||||
work_hours: entry.work_hours
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('개별 항목 삭제 오류:', err);
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
@@ -350,115 +311,84 @@ const getSelectQuery = () => `
|
||||
/**
|
||||
* 7. ID로 작업보고서 조회
|
||||
*/
|
||||
const getById = async (id, callback) => {
|
||||
try {
|
||||
const getById = async (id) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + 'WHERE dwr.id = ?';
|
||||
const [rows] = await db.query(sql, [id]);
|
||||
callback(null, rows[0] || null);
|
||||
} catch (err) {
|
||||
console.error('ID로 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows[0] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 8. 일일 작업보고서 조회 (날짜별)
|
||||
*/
|
||||
const getByDate = async (date, callback) => {
|
||||
try {
|
||||
const getByDate = async (date) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + `
|
||||
WHERE dwr.report_date = ?
|
||||
ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('날짜별 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 9. 일일 작업보고서 조회 (날짜 + 작성자별)
|
||||
*/
|
||||
const getByDateAndCreator = async (date, created_by, callback) => {
|
||||
try {
|
||||
const getByDateAndCreator = async (date, created_by) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + `
|
||||
WHERE dwr.report_date = ? AND dwr.created_by = ?
|
||||
ORDER BY w.worker_name ASC, p.project_name ASC, dwr.id ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date, created_by]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('날짜+작성자별 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 10. 일일 작업보고서 조회 (작업자별)
|
||||
*/
|
||||
const getByWorker = async (worker_id, callback) => {
|
||||
try {
|
||||
const getByWorker = async (worker_id) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + `
|
||||
WHERE dwr.worker_id = ?
|
||||
ORDER BY dwr.report_date DESC, dwr.id ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [worker_id]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('작업자별 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 11. 일일 작업보고서 조회 (날짜 + 작업자)
|
||||
*/
|
||||
const getByDateAndWorker = async (date, worker_id, callback) => {
|
||||
try {
|
||||
const getByDateAndWorker = async (date, worker_id) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + `
|
||||
WHERE dwr.report_date = ? AND dwr.worker_id = ?
|
||||
ORDER BY dwr.id ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date, worker_id]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('날짜+작업자별 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 12. 기간별 조회
|
||||
*/
|
||||
const getByRange = async (start_date, end_date, callback) => {
|
||||
try {
|
||||
const getByRange = async (start_date, end_date) => {
|
||||
const db = await getDb();
|
||||
const sql = getSelectQuery() + `
|
||||
WHERE dwr.report_date BETWEEN ? AND ?
|
||||
ORDER BY dwr.report_date DESC, w.worker_name ASC, dwr.id ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [start_date, end_date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('기간별 작업보고서 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
// 조건 구성
|
||||
@@ -507,18 +437,13 @@ const searchWithDetails = async (params, callback) => {
|
||||
const dataParams = [...queryParams, limit, offset];
|
||||
const [rows] = await db.query(dataQuery, dataParams);
|
||||
|
||||
callback(null, { reports: rows, total });
|
||||
} catch (err) {
|
||||
console.error('상세 검색 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return { reports: rows, total };
|
||||
};
|
||||
|
||||
/**
|
||||
* 14. 일일 근무 요약 조회 (날짜별)
|
||||
*/
|
||||
const getSummaryByDate = async (date, callback) => {
|
||||
try {
|
||||
const getSummaryByDate = async (date) => {
|
||||
const db = await getDb();
|
||||
|
||||
const sql = `
|
||||
@@ -536,18 +461,13 @@ const getSummaryByDate = async (date, callback) => {
|
||||
ORDER BY w.worker_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('일일 근무 요약 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 15. 일일 근무 요약 조회 (작업자별)
|
||||
*/
|
||||
const getSummaryByWorker = async (worker_id, callback) => {
|
||||
try {
|
||||
const getSummaryByWorker = async (worker_id) => {
|
||||
const db = await getDb();
|
||||
|
||||
const sql = `
|
||||
@@ -565,18 +485,13 @@ const getSummaryByWorker = async (worker_id, callback) => {
|
||||
ORDER BY dwr.report_date DESC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [worker_id]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('작업자별 근무 요약 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 16. 월간 요약
|
||||
*/
|
||||
const getMonthlySummary = async (year, month, callback) => {
|
||||
try {
|
||||
const getMonthlySummary = async (year, month) => {
|
||||
const db = await getDb();
|
||||
const start = `${year.padStart(4, '0')}-${month.padStart(2, '0')}-01`;
|
||||
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
|
||||
`;
|
||||
const [rows] = await db.query(sql, [start, end]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
console.error('월간 요약 조회 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 17. 작업보고서 수정
|
||||
*/
|
||||
const updateById = async (id, updateData, callback) => {
|
||||
try {
|
||||
const updateById = async (id, updateData) => {
|
||||
const db = await getDb();
|
||||
|
||||
const setFields = [];
|
||||
@@ -655,8 +565,6 @@ const updateById = async (id, updateData, callback) => {
|
||||
const sql = `UPDATE daily_work_reports SET ${setFields.join(', ')} WHERE id = ?`;
|
||||
const [result] = await db.query(sql, values);
|
||||
|
||||
|
||||
|
||||
// [Sync] 근태 기록 동기화
|
||||
try {
|
||||
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);
|
||||
}
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
console.error('작업보고서 수정 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 18. 특정 작업보고서 삭제
|
||||
*/
|
||||
const removeById = async (id, deletedBy, callback) => {
|
||||
const removeById = async (id, deletedBy) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -708,7 +612,6 @@ const removeById = async (id, deletedBy, callback) => {
|
||||
|
||||
await conn.commit();
|
||||
|
||||
|
||||
// [Sync] 근태 기록 동기화
|
||||
if (reportInfo.length > 0) {
|
||||
try {
|
||||
@@ -720,11 +623,10 @@ const removeById = async (id, deletedBy, callback) => {
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
return result.affectedRows;
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('작업보고서 삭제 오류:', err);
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
@@ -733,7 +635,7 @@ const removeById = async (id, deletedBy, callback) => {
|
||||
/**
|
||||
* 19. 작업자의 특정 날짜 전체 삭제
|
||||
*/
|
||||
const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
|
||||
const removeByDateAndWorker = async (date, worker_id, deletedBy) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -768,7 +670,6 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
|
||||
|
||||
await conn.commit();
|
||||
|
||||
|
||||
// [Sync] 근태 기록 동기화
|
||||
try {
|
||||
const AttendanceModel = require('./attendanceModel');
|
||||
@@ -777,21 +678,19 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => {
|
||||
console.error('근태 기록 동기화 실패 (Batch Delete):', syncErr);
|
||||
}
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
return result.affectedRows;
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('작업보고서 전체 삭제 오류:', err);
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 20. 통계 조회 (Promise 기반)
|
||||
* 20. 통계 조회
|
||||
*/
|
||||
const getStatistics = async (start_date, end_date) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
const overallSql = `
|
||||
@@ -821,14 +720,10 @@ const getStatistics = async (start_date, end_date) => {
|
||||
overall: overallRows[0],
|
||||
daily_breakdown: dailyStats
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('통계 조회 오류:', err);
|
||||
throw new Error('데이터베이스에서 통계 정보를 조회하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다. (Promise 기반)
|
||||
* [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다.
|
||||
* @param {object} modelData - 서비스 레이어에서 전달된 데이터
|
||||
* @returns {Promise<object>} 삽입된 항목의 ID 배열과 개수
|
||||
*/
|
||||
@@ -862,8 +757,6 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => {
|
||||
|
||||
await conn.commit();
|
||||
|
||||
|
||||
|
||||
// [Sync] 근태 기록 동기화
|
||||
try {
|
||||
const AttendanceModel = require('./attendanceModel');
|
||||
@@ -879,9 +772,7 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => {
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('[Model] 작업 보고서 생성 중 오류 발생:', err);
|
||||
// 여기서 발생한 에러는 서비스 레이어로 전파됩니다.
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw new Error('데이터베이스에 작업 보고서를 생성하는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
conn.release();
|
||||
@@ -925,7 +816,7 @@ const getSelectQueryV2 = () => `
|
||||
`;
|
||||
|
||||
/**
|
||||
* [V2] 옵션 기반으로 작업 보고서를 조회합니다. (Promise 기반)
|
||||
* [V2] 옵션 기반으로 작업 보고서를 조회합니다.
|
||||
* @param {object} options - 조회 조건 (date, worker_id, created_by_user_id 등)
|
||||
* @returns {Promise<Array>} 조회된 작업 보고서 배열
|
||||
*/
|
||||
@@ -951,7 +842,6 @@ const getReportsWithOptions = async (options) => {
|
||||
whereConditions.push('dwr.created_by = ?');
|
||||
queryParams.push(options.created_by_user_id);
|
||||
}
|
||||
// 필요에 따라 다른 조건 추가 가능 (project_id 등)
|
||||
|
||||
if (whereConditions.length === 0) {
|
||||
throw new Error('조회 조건이 하나 이상 필요합니다.');
|
||||
@@ -960,17 +850,12 @@ const getReportsWithOptions = async (options) => {
|
||||
const whereClause = whereConditions.join(' AND ');
|
||||
const sql = `${getSelectQueryV2()} WHERE ${whereClause} ORDER BY w.worker_name ASC, p.project_name ASC`;
|
||||
|
||||
try {
|
||||
const [rows] = await db.query(sql, queryParams);
|
||||
return rows;
|
||||
} catch (err) {
|
||||
console.error('[Model] 작업 보고서 조회 오류:', err);
|
||||
throw new Error('데이터베이스에서 작업 보고서를 조회하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다. (Promise 기반)
|
||||
* [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다.
|
||||
* @param {string} reportId - 수정할 보고서의 ID
|
||||
* @param {object} updateData - 수정할 필드와 값
|
||||
* @returns {Promise<number>} 영향을 받은 행의 수
|
||||
@@ -1011,7 +896,6 @@ const updateReportById = async (reportId, updateData) => {
|
||||
|
||||
const sql = `UPDATE daily_work_reports SET ${setClauses.join(', ')} WHERE id = ?`;
|
||||
|
||||
try {
|
||||
const [result] = await db.query(sql, queryParams);
|
||||
|
||||
// [Sync] 근태 기록 동기화
|
||||
@@ -1025,14 +909,10 @@ const updateReportById = async (reportId, updateData) => {
|
||||
}
|
||||
|
||||
return result.affectedRows;
|
||||
} catch (err) {
|
||||
console.error(`[Model] 작업 보고서 수정 오류 (id: ${reportId}):`, err);
|
||||
throw new Error('데이터베이스에서 작업 보고서를 수정하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다. (Promise 기반)
|
||||
* [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다.
|
||||
* @param {string} reportId - 삭제할 보고서 ID
|
||||
* @param {number} deletedByUserId - 삭제를 수행하는 사용자 ID
|
||||
* @returns {Promise<number>} 영향을 받은 행의 수
|
||||
@@ -1071,9 +951,8 @@ const removeReportById = async (reportId, deletedByUserId) => {
|
||||
return result.affectedRows;
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error(`[Model] 작업 보고서 삭제 오류 (id: ${reportId}):`, err);
|
||||
throw new Error('데이터베이스에서 작업 보고서를 삭제하는 중 오류가 발생했습니다.');
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
@@ -1082,153 +961,108 @@ const removeReportById = async (reportId, deletedByUserId) => {
|
||||
// ========== 마스터 데이터 CRUD 메서드들 ==========
|
||||
|
||||
/**
|
||||
* 📝 작업 유형 생성
|
||||
* 작업 유형 생성
|
||||
*/
|
||||
const createWorkType = async (data, callback) => {
|
||||
try {
|
||||
const createWorkType = async (data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, category } = data;
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO work_types (name, description, category) VALUES (?, ?, ?)',
|
||||
[name, description, category]
|
||||
);
|
||||
callback(null, { id: result.insertId, ...data });
|
||||
} catch (err) {
|
||||
console.error('작업 유형 생성 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return { id: result.insertId, ...data };
|
||||
};
|
||||
|
||||
/**
|
||||
* ✏️ 작업 유형 수정
|
||||
* 작업 유형 수정
|
||||
*/
|
||||
const updateWorkType = async (id, data, callback) => {
|
||||
try {
|
||||
const updateWorkType = async (id, data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, category } = data;
|
||||
const [result] = await db.query(
|
||||
'UPDATE work_types SET name = ?, description = ?, category = ? WHERE id = ?',
|
||||
[name, description, category, id]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('작업 유형 수정 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 🗑️ 작업 유형 삭제
|
||||
* 작업 유형 삭제
|
||||
*/
|
||||
const deleteWorkType = async (id, callback) => {
|
||||
try {
|
||||
const deleteWorkType = async (id) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query('DELETE FROM work_types WHERE id = ?', [id]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('작업 유형 삭제 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 📝 작업 상태 생성
|
||||
* 작업 상태 생성
|
||||
*/
|
||||
const createWorkStatus = async (data, callback) => {
|
||||
try {
|
||||
const createWorkStatus = async (data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, is_error } = data;
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO work_status_types (name, description, is_error) VALUES (?, ?, ?)',
|
||||
[name, description, is_error || 0]
|
||||
);
|
||||
callback(null, { id: result.insertId, ...data });
|
||||
} catch (err) {
|
||||
console.error('작업 상태 생성 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return { id: result.insertId, ...data };
|
||||
};
|
||||
|
||||
/**
|
||||
* ✏️ 작업 상태 수정
|
||||
* 작업 상태 수정
|
||||
*/
|
||||
const updateWorkStatus = async (id, data, callback) => {
|
||||
try {
|
||||
const updateWorkStatus = async (id, data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, is_error } = data;
|
||||
const [result] = await db.query(
|
||||
'UPDATE work_status_types SET name = ?, description = ?, is_error = ? WHERE id = ?',
|
||||
[name, description, is_error || 0, id]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('작업 상태 수정 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 🗑️ 작업 상태 삭제
|
||||
* 작업 상태 삭제
|
||||
*/
|
||||
const deleteWorkStatus = async (id, callback) => {
|
||||
try {
|
||||
const deleteWorkStatus = async (id) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query('DELETE FROM work_status_types WHERE id = ?', [id]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('작업 상태 삭제 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 📝 오류 유형 생성
|
||||
* 오류 유형 생성
|
||||
*/
|
||||
const createErrorType = async (data, callback) => {
|
||||
try {
|
||||
const createErrorType = async (data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, severity } = data;
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO error_types (name, description, severity) VALUES (?, ?, ?)',
|
||||
[name, description, severity || 'medium']
|
||||
);
|
||||
callback(null, { id: result.insertId, ...data });
|
||||
} catch (err) {
|
||||
console.error('오류 유형 생성 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return { id: result.insertId, ...data };
|
||||
};
|
||||
|
||||
/**
|
||||
* ✏️ 오류 유형 수정
|
||||
* 오류 유형 수정
|
||||
*/
|
||||
const updateErrorType = async (id, data, callback) => {
|
||||
try {
|
||||
const updateErrorType = async (id, data) => {
|
||||
const db = await getDb();
|
||||
const { name, description, severity } = data;
|
||||
const [result] = await db.query(
|
||||
'UPDATE error_types SET name = ?, description = ?, severity = ? WHERE id = ?',
|
||||
[name, description, severity || 'medium', id]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('오류 유형 수정 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 🗑️ 오류 유형 삭제
|
||||
* 오류 유형 삭제
|
||||
*/
|
||||
const deleteErrorType = async (id, callback) => {
|
||||
try {
|
||||
const deleteErrorType = async (id) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query('DELETE FROM error_types WHERE id = ?', [id]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
console.error('오류 유형 삭제 오류:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
@@ -1331,30 +1165,28 @@ const createFromTbmAssignment = async (reportData) => {
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('[Model] TBM 작업보고서 생성 중 오류 발생:', err);
|
||||
throw new Error('TBM 작업보고서 생성 중 오류가 발생했습니다.');
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
// 모든 함수 내보내기 (Promise 기반 함수 위주로 재구성)
|
||||
// 모든 함수 내보내기
|
||||
module.exports = {
|
||||
// 새로 추가된 V2 함수 (Promise 기반)
|
||||
// V2 함수
|
||||
createReportEntries,
|
||||
getReportsWithOptions,
|
||||
updateReportById,
|
||||
removeReportById,
|
||||
createFromTbmAssignment,
|
||||
|
||||
// Promise 기반으로 리팩토링된 함수
|
||||
// 통계/요약
|
||||
getStatistics,
|
||||
getSummaryByDate,
|
||||
getSummaryByWorker,
|
||||
|
||||
// 아직 리팩토링되지 않았지만 필요한 기존 함수들...
|
||||
// (점진적으로 아래 함수들도 Promise 기반으로 전환해야 함)
|
||||
// 마스터 데이터 조회
|
||||
getAllWorkTypes,
|
||||
getAllWorkStatusTypes,
|
||||
getAllErrorTypes,
|
||||
@@ -1369,6 +1201,8 @@ module.exports = {
|
||||
createErrorType,
|
||||
updateErrorType,
|
||||
deleteErrorType,
|
||||
|
||||
// 레거시 함수 (콜백 제거 완료)
|
||||
createDailyReport,
|
||||
getMyAccumulatedHours,
|
||||
getAccumulatedReportsByDate,
|
||||
|
||||
@@ -4,8 +4,7 @@ const notificationModel = require('./notificationModel');
|
||||
|
||||
const EquipmentModel = {
|
||||
// CREATE - 설비 생성
|
||||
create: async (equipmentData, callback) => {
|
||||
try {
|
||||
create: async (equipmentData) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
INSERT INTO equipments (
|
||||
@@ -37,18 +36,14 @@ const EquipmentModel = {
|
||||
];
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
callback(null, {
|
||||
return {
|
||||
equipment_id: result.insertId,
|
||||
...equipmentData
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// READ ALL - 모든 설비 조회 (필터링 옵션 포함)
|
||||
getAll: async (filters, callback) => {
|
||||
try {
|
||||
getAll: async (filters) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -63,25 +58,21 @@ const EquipmentModel = {
|
||||
|
||||
const values = [];
|
||||
|
||||
// 필터링: 작업장 ID
|
||||
if (filters.workplace_id) {
|
||||
query += ' AND e.workplace_id = ?';
|
||||
values.push(filters.workplace_id);
|
||||
}
|
||||
|
||||
// 필터링: 설비 유형
|
||||
if (filters.equipment_type) {
|
||||
query += ' AND e.equipment_type = ?';
|
||||
values.push(filters.equipment_type);
|
||||
}
|
||||
|
||||
// 필터링: 상태
|
||||
if (filters.status) {
|
||||
query += ' AND e.status = ?';
|
||||
values.push(filters.status);
|
||||
}
|
||||
|
||||
// 필터링: 검색어 (설비명, 설비코드)
|
||||
if (filters.search) {
|
||||
query += ' AND (e.equipment_name LIKE ? OR e.equipment_code LIKE ?)';
|
||||
const searchTerm = `%${filters.search}%`;
|
||||
@@ -91,58 +82,44 @@ const EquipmentModel = {
|
||||
query += ' ORDER BY e.equipment_code ASC';
|
||||
|
||||
const [rows] = await db.query(query, values);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
|
||||
// READ ONE - 특정 설비 조회
|
||||
getById: async (equipmentId, callback) => {
|
||||
try {
|
||||
getById: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
wc.category_name
|
||||
FROM equipments e
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE e.equipment_id = ?
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows[0]);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
WHERE e.equipment_id = ?`,
|
||||
[equipmentId]
|
||||
);
|
||||
return rows[0];
|
||||
},
|
||||
|
||||
// READ BY WORKPLACE - 특정 작업장의 설비 조회
|
||||
getByWorkplace: async (workplaceId, callback) => {
|
||||
try {
|
||||
getByWorkplace: async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT e.*
|
||||
const [rows] = await db.query(
|
||||
`SELECT e.*
|
||||
FROM equipments e
|
||||
WHERE e.workplace_id = ?
|
||||
ORDER BY e.equipment_code ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [workplaceId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY e.equipment_code ASC`,
|
||||
[workplaceId]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// READ ACTIVE - 활성 설비만 조회
|
||||
getActive: async (callback) => {
|
||||
try {
|
||||
getActive: async () => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
wc.category_name
|
||||
@@ -150,43 +127,14 @@ const EquipmentModel = {
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE e.status = 'active'
|
||||
ORDER BY e.equipment_code ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY e.equipment_code ASC`
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// UPDATE - 설비 수정
|
||||
update: async (equipmentId, equipmentData, callback) => {
|
||||
try {
|
||||
update: async (equipmentId, equipmentData) => {
|
||||
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 = [
|
||||
equipmentData.equipment_code,
|
||||
equipmentData.equipment_name,
|
||||
@@ -208,22 +156,40 @@ const EquipmentModel = {
|
||||
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) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId, ...equipmentData });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
throw new Error('Equipment not found');
|
||||
}
|
||||
return { equipment_id: equipmentId, ...equipmentData };
|
||||
},
|
||||
|
||||
// UPDATE MAP POSITION - 지도상 위치 업데이트 (선택적으로 workplace_id도 업데이트)
|
||||
updateMapPosition: async (equipmentId, positionData, callback) => {
|
||||
try {
|
||||
// UPDATE MAP POSITION - 지도상 위치 업데이트
|
||||
updateMapPosition: async (equipmentId, positionData) => {
|
||||
const db = await getDb();
|
||||
|
||||
// workplace_id가 포함된 경우 함께 업데이트
|
||||
const hasWorkplaceId = positionData.workplace_id !== undefined;
|
||||
|
||||
const query = hasWorkplaceId ? `
|
||||
@@ -262,33 +228,23 @@ const EquipmentModel = {
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId, ...positionData });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
throw new Error('Equipment not found');
|
||||
}
|
||||
return { equipment_id: equipmentId, ...positionData };
|
||||
},
|
||||
|
||||
// DELETE - 설비 삭제
|
||||
delete: async (equipmentId, callback) => {
|
||||
try {
|
||||
delete: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = 'DELETE FROM equipments WHERE equipment_id = ?';
|
||||
|
||||
const [result] = await db.query(query, [equipmentId]);
|
||||
const [result] = await db.query('DELETE FROM equipments WHERE equipment_id = ?', [equipmentId]);
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
throw new Error('Equipment not found');
|
||||
}
|
||||
return { equipment_id: equipmentId };
|
||||
},
|
||||
|
||||
// CHECK DUPLICATE CODE - 설비 코드 중복 확인
|
||||
checkDuplicateCode: async (equipmentCode, excludeEquipmentId, callback) => {
|
||||
try {
|
||||
checkDuplicateCode: async (equipmentCode, excludeEquipmentId) => {
|
||||
const db = await getDb();
|
||||
let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?';
|
||||
const values = [equipmentCode];
|
||||
@@ -299,48 +255,35 @@ const EquipmentModel = {
|
||||
}
|
||||
|
||||
const [rows] = await db.query(query, values);
|
||||
callback(null, rows.length > 0);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return rows.length > 0;
|
||||
},
|
||||
|
||||
// GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회
|
||||
getEquipmentTypes: async (callback) => {
|
||||
try {
|
||||
getEquipmentTypes: async () => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT DISTINCT equipment_type
|
||||
const [rows] = await db.query(
|
||||
`SELECT DISTINCT equipment_type
|
||||
FROM equipments
|
||||
WHERE equipment_type IS NOT NULL
|
||||
ORDER BY equipment_type ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows.map(row => row.equipment_type));
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY equipment_type ASC`
|
||||
);
|
||||
return rows.map(row => row.equipment_type);
|
||||
},
|
||||
|
||||
// GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 (TKP-001 형식)
|
||||
getNextEquipmentCode: async (prefix = 'TKP', callback) => {
|
||||
try {
|
||||
// GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성
|
||||
getNextEquipmentCode: async (prefix = 'TKP') => {
|
||||
const db = await getDb();
|
||||
// 해당 접두사로 시작하는 가장 큰 번호 찾기
|
||||
const query = `
|
||||
SELECT equipment_code
|
||||
const [rows] = await db.query(
|
||||
`SELECT equipment_code
|
||||
FROM equipments
|
||||
WHERE equipment_code LIKE ?
|
||||
ORDER BY equipment_code DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [`${prefix}-%`]);
|
||||
LIMIT 1`,
|
||||
[`${prefix}-%`]
|
||||
);
|
||||
|
||||
let nextNumber = 1;
|
||||
if (rows.length > 0) {
|
||||
// TKP-001 형식에서 숫자 부분 추출
|
||||
const lastCode = rows[0].equipment_code;
|
||||
const match = lastCode.match(new RegExp(`^${prefix}-(\\d+)$`));
|
||||
if (match) {
|
||||
@@ -348,28 +291,15 @@ const EquipmentModel = {
|
||||
}
|
||||
}
|
||||
|
||||
// 3자리로 패딩 (001, 002, ...)
|
||||
const nextCode = `${prefix}-${String(nextNumber).padStart(3, '0')}`;
|
||||
callback(null, nextCode);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return `${prefix}-${String(nextNumber).padStart(3, '0')}`;
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// 설비 사진 관리
|
||||
// ==========================================
|
||||
|
||||
// ADD PHOTO - 설비 사진 추가
|
||||
addPhoto: async (equipmentId, photoData, callback) => {
|
||||
try {
|
||||
addPhoto: async (equipmentId, photoData) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
INSERT INTO equipment_photos (
|
||||
equipment_id, photo_path, description, display_order, uploaded_by
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
equipmentId,
|
||||
photoData.photo_path,
|
||||
@@ -378,72 +308,59 @@ const EquipmentModel = {
|
||||
photoData.uploaded_by || null
|
||||
];
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
callback(null, {
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO equipment_photos (
|
||||
equipment_id, photo_path, description, display_order, uploaded_by
|
||||
) VALUES (?, ?, ?, ?, ?)`,
|
||||
values
|
||||
);
|
||||
|
||||
return {
|
||||
photo_id: result.insertId,
|
||||
equipment_id: equipmentId,
|
||||
...photoData
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// GET PHOTOS - 설비 사진 조회
|
||||
getPhotos: async (equipmentId, callback) => {
|
||||
try {
|
||||
getPhotos: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT ep.*, u.name AS uploaded_by_name
|
||||
const [rows] = await db.query(
|
||||
`SELECT ep.*, u.name AS uploaded_by_name
|
||||
FROM equipment_photos ep
|
||||
LEFT JOIN users u ON ep.uploaded_by = u.user_id
|
||||
WHERE ep.equipment_id = ?
|
||||
ORDER BY ep.display_order ASC, ep.created_at ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY ep.display_order ASC, ep.created_at ASC`,
|
||||
[equipmentId]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// DELETE PHOTO - 설비 사진 삭제
|
||||
deletePhoto: async (photoId, callback) => {
|
||||
try {
|
||||
deletePhoto: async (photoId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 먼저 사진 정보 조회 (파일 삭제용)
|
||||
const [photo] = await db.query(
|
||||
'SELECT photo_path FROM equipment_photos WHERE photo_id = ?',
|
||||
[photoId]
|
||||
);
|
||||
|
||||
const query = 'DELETE FROM equipment_photos WHERE photo_id = ?';
|
||||
const [result] = await db.query(query, [photoId]);
|
||||
const [result] = await db.query('DELETE FROM equipment_photos WHERE photo_id = ?', [photoId]);
|
||||
|
||||
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 });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return { photo_id: photoId, photo_path: photo[0]?.photo_path };
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// 설비 임시 이동
|
||||
// ==========================================
|
||||
|
||||
// MOVE TEMPORARILY - 설비 임시 이동
|
||||
moveTemporarily: async (equipmentId, moveData, callback) => {
|
||||
try {
|
||||
moveTemporarily: async (equipmentId, moveData) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 설비 현재 위치 업데이트
|
||||
const updateQuery = `
|
||||
UPDATE equipments SET
|
||||
const [result] = await db.query(
|
||||
`UPDATE equipments SET
|
||||
current_workplace_id = ?,
|
||||
current_map_x_percent = ?,
|
||||
current_map_y_percent = ?,
|
||||
@@ -453,10 +370,8 @@ const EquipmentModel = {
|
||||
moved_at = NOW(),
|
||||
moved_by = ?,
|
||||
updated_at = NOW()
|
||||
WHERE equipment_id = ?
|
||||
`;
|
||||
|
||||
const updateValues = [
|
||||
WHERE equipment_id = ?`,
|
||||
[
|
||||
moveData.target_workplace_id,
|
||||
moveData.target_x_percent,
|
||||
moveData.target_y_percent,
|
||||
@@ -464,26 +379,22 @@ const EquipmentModel = {
|
||||
moveData.target_height_percent || null,
|
||||
moveData.moved_by || null,
|
||||
equipmentId
|
||||
];
|
||||
|
||||
const [result] = await db.query(updateQuery, updateValues);
|
||||
]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
throw new Error('Equipment not found');
|
||||
}
|
||||
|
||||
// 2. 이동 이력 기록
|
||||
const logQuery = `
|
||||
INSERT INTO equipment_move_logs (
|
||||
await db.query(
|
||||
`INSERT INTO equipment_move_logs (
|
||||
equipment_id, move_type,
|
||||
from_workplace_id, to_workplace_id,
|
||||
from_x_percent, from_y_percent,
|
||||
to_x_percent, to_y_percent,
|
||||
reason, moved_by
|
||||
) VALUES (?, 'temporary', ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
await db.query(logQuery, [
|
||||
) VALUES (?, 'temporary', ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
equipmentId,
|
||||
moveData.from_workplace_id || null,
|
||||
moveData.target_workplace_id,
|
||||
@@ -493,32 +404,26 @@ const EquipmentModel = {
|
||||
moveData.target_y_percent,
|
||||
moveData.reason || null,
|
||||
moveData.moved_by || null
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, { equipment_id: equipmentId, moved: true });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return { equipment_id: equipmentId, moved: true };
|
||||
},
|
||||
|
||||
// RETURN TO ORIGINAL - 설비 원위치 복귀
|
||||
returnToOriginal: async (equipmentId, userId, callback) => {
|
||||
try {
|
||||
returnToOriginal: async (equipmentId, userId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 현재 임시 위치 정보 조회
|
||||
const [equipment] = await db.query(
|
||||
'SELECT current_workplace_id, current_map_x_percent, current_map_y_percent FROM equipments WHERE equipment_id = ?',
|
||||
[equipmentId]
|
||||
);
|
||||
|
||||
if (!equipment[0]) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
throw new Error('Equipment not found');
|
||||
}
|
||||
|
||||
// 2. 임시 위치 필드 초기화
|
||||
const updateQuery = `
|
||||
UPDATE equipments SET
|
||||
await db.query(
|
||||
`UPDATE equipments SET
|
||||
current_workplace_id = NULL,
|
||||
current_map_x_percent = NULL,
|
||||
current_map_y_percent = NULL,
|
||||
@@ -528,40 +433,32 @@ const EquipmentModel = {
|
||||
moved_at = NULL,
|
||||
moved_by = NULL,
|
||||
updated_at = NOW()
|
||||
WHERE equipment_id = ?
|
||||
`;
|
||||
WHERE equipment_id = ?`,
|
||||
[equipmentId]
|
||||
);
|
||||
|
||||
await db.query(updateQuery, [equipmentId]);
|
||||
|
||||
// 3. 복귀 이력 기록
|
||||
const logQuery = `
|
||||
INSERT INTO equipment_move_logs (
|
||||
await db.query(
|
||||
`INSERT INTO equipment_move_logs (
|
||||
equipment_id, move_type,
|
||||
from_workplace_id, from_x_percent, from_y_percent,
|
||||
reason, moved_by
|
||||
) VALUES (?, 'return', ?, ?, ?, '원위치 복귀', ?)
|
||||
`;
|
||||
|
||||
await db.query(logQuery, [
|
||||
) VALUES (?, 'return', ?, ?, ?, '원위치 복귀', ?)`,
|
||||
[
|
||||
equipmentId,
|
||||
equipment[0].current_workplace_id,
|
||||
equipment[0].current_map_x_percent,
|
||||
equipment[0].current_map_y_percent,
|
||||
userId || null
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, { equipment_id: equipmentId, returned: true });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return { equipment_id: equipmentId, returned: true };
|
||||
},
|
||||
|
||||
// GET TEMPORARILY MOVED - 임시 이동된 설비 목록
|
||||
getTemporarilyMoved: async (callback) => {
|
||||
try {
|
||||
getTemporarilyMoved: async () => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
e.*,
|
||||
w_orig.workplace_name AS original_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 users u ON e.moved_by = u.user_id
|
||||
WHERE e.is_temporarily_moved = TRUE
|
||||
ORDER BY e.moved_at DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY e.moved_at DESC`
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// GET MOVE LOGS - 설비 이동 이력 조회
|
||||
getMoveLogs: async (equipmentId, callback) => {
|
||||
try {
|
||||
getMoveLogs: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
eml.*,
|
||||
w_from.workplace_name AS from_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 users u ON eml.moved_by = u.user_id
|
||||
WHERE eml.equipment_id = ?
|
||||
ORDER BY eml.moved_at DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY eml.moved_at DESC`,
|
||||
[equipmentId]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// 설비 외부 반출/반입
|
||||
// ==========================================
|
||||
|
||||
// EXPORT EQUIPMENT - 설비 외부 반출
|
||||
exportEquipment: async (exportData, callback) => {
|
||||
try {
|
||||
exportEquipment: async (exportData) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 반출 로그 생성
|
||||
const logQuery = `
|
||||
INSERT INTO equipment_external_logs (
|
||||
const [logResult] = await db.query(
|
||||
`INSERT INTO equipment_external_logs (
|
||||
equipment_id, log_type, export_date, expected_return_date,
|
||||
destination, reason, notes, exported_by
|
||||
) VALUES (?, 'export', ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const logValues = [
|
||||
) VALUES (?, 'export', ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
exportData.equipment_id,
|
||||
exportData.export_date || new Date().toISOString().slice(0, 10),
|
||||
exportData.expected_return_date || null,
|
||||
@@ -631,45 +512,36 @@ const EquipmentModel = {
|
||||
exportData.reason || null,
|
||||
exportData.notes || null,
|
||||
exportData.exported_by || null
|
||||
];
|
||||
]
|
||||
);
|
||||
|
||||
const [logResult] = await db.query(logQuery, logValues);
|
||||
|
||||
// 2. 설비 상태 업데이트
|
||||
const status = exportData.is_repair ? 'repair_external' : 'external';
|
||||
await db.query(
|
||||
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
|
||||
[status, exportData.equipment_id]
|
||||
);
|
||||
|
||||
callback(null, {
|
||||
return {
|
||||
log_id: logResult.insertId,
|
||||
equipment_id: exportData.equipment_id,
|
||||
exported: true
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// RETURN EQUIPMENT - 설비 반입 (외부에서 복귀)
|
||||
returnEquipment: async (logId, returnData, callback) => {
|
||||
try {
|
||||
returnEquipment: async (logId, returnData) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 반출 로그 조회
|
||||
const [logs] = await db.query(
|
||||
'SELECT equipment_id FROM equipment_external_logs WHERE log_id = ?',
|
||||
[logId]
|
||||
);
|
||||
|
||||
if (!logs[0]) {
|
||||
return callback(new Error('Export log not found'));
|
||||
throw new Error('Export log not found');
|
||||
}
|
||||
|
||||
const equipmentId = logs[0].equipment_id;
|
||||
|
||||
// 2. 반출 로그 업데이트
|
||||
await db.query(
|
||||
`UPDATE equipment_external_logs SET
|
||||
actual_return_date = ?,
|
||||
@@ -685,28 +557,22 @@ const EquipmentModel = {
|
||||
]
|
||||
);
|
||||
|
||||
// 3. 설비 상태 복원
|
||||
await db.query(
|
||||
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
|
||||
[returnData.new_status || 'active', equipmentId]
|
||||
);
|
||||
|
||||
callback(null, {
|
||||
return {
|
||||
log_id: logId,
|
||||
equipment_id: equipmentId,
|
||||
returned: true
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// GET EXTERNAL LOGS - 설비 외부 반출 이력 조회
|
||||
getExternalLogs: async (equipmentId, callback) => {
|
||||
try {
|
||||
getExternalLogs: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
eel.*,
|
||||
u_exp.name AS exported_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_ret ON eel.returned_by = u_ret.user_id
|
||||
WHERE eel.equipment_id = ?
|
||||
ORDER BY eel.created_at DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY eel.created_at DESC`,
|
||||
[equipmentId]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// GET EXPORTED EQUIPMENTS - 현재 외부 반출 중인 설비 목록
|
||||
getExportedEquipments: async (callback) => {
|
||||
try {
|
||||
getExportedEquipments: async () => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
eel.export_date,
|
||||
@@ -748,37 +608,28 @@ const EquipmentModel = {
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN users u ON eel.exported_by = u.user_id
|
||||
WHERE e.status IN ('external', 'repair_external')
|
||||
ORDER BY eel.export_date DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY eel.export_date DESC`
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// 설비 수리 신청 (work_issue_reports 연동)
|
||||
// 설비 수리 신청
|
||||
// ==========================================
|
||||
|
||||
// CREATE REPAIR REQUEST - 수리 신청 (신고 시스템 활용)
|
||||
createRepairRequest: async (requestData, callback) => {
|
||||
try {
|
||||
createRepairRequest: async (requestData) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 설비 수리 카테고리 ID 조회
|
||||
const [categories] = await db.query(
|
||||
"SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리' LIMIT 1"
|
||||
);
|
||||
|
||||
if (!categories[0]) {
|
||||
return callback(new Error('설비 수리 카테고리가 없습니다'));
|
||||
throw new Error('설비 수리 카테고리가 없습니다');
|
||||
}
|
||||
|
||||
const categoryId = categories[0].category_id;
|
||||
|
||||
// 항목 ID 조회 (지정된 항목이 없으면 첫번째 항목 사용)
|
||||
let itemId = requestData.item_id;
|
||||
if (!itemId) {
|
||||
const [items] = await db.query(
|
||||
@@ -788,21 +639,17 @@ const EquipmentModel = {
|
||||
itemId = items[0]?.item_id;
|
||||
}
|
||||
|
||||
// 사진 경로 분리 (최대 5장)
|
||||
const photos = requestData.photo_paths || [];
|
||||
|
||||
// work_issue_reports에 삽입
|
||||
const query = `
|
||||
INSERT INTO work_issue_reports (
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO work_issue_reports (
|
||||
reporter_id, issue_category_id, issue_item_id,
|
||||
workplace_id, equipment_id,
|
||||
additional_description,
|
||||
photo_path1, photo_path2, photo_path3, photo_path4, photo_path5,
|
||||
status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'reported')
|
||||
`;
|
||||
|
||||
const values = [
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'reported')`,
|
||||
[
|
||||
requestData.reported_by || null,
|
||||
categoryId,
|
||||
itemId,
|
||||
@@ -814,17 +661,14 @@ const EquipmentModel = {
|
||||
photos[2] || null,
|
||||
photos[3] || null,
|
||||
photos[4] || null
|
||||
];
|
||||
]
|
||||
);
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
|
||||
// 설비 상태를 repair_needed로 업데이트
|
||||
await db.query(
|
||||
'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?',
|
||||
['repair_needed', requestData.equipment_id]
|
||||
);
|
||||
|
||||
// 알림 생성
|
||||
try {
|
||||
await notificationModel.createRepairNotification({
|
||||
equipment_id: requestData.equipment_id,
|
||||
@@ -834,26 +678,20 @@ const EquipmentModel = {
|
||||
created_by: requestData.reported_by
|
||||
});
|
||||
} catch (notifError) {
|
||||
console.error('알림 생성 실패:', notifError);
|
||||
// 알림 생성 실패해도 수리 신청은 성공으로 처리
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
return {
|
||||
report_id: result.insertId,
|
||||
equipment_id: requestData.equipment_id,
|
||||
created: true
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// GET REPAIR HISTORY - 설비 수리 이력 조회
|
||||
getRepairHistory: async (equipmentId, callback) => {
|
||||
try {
|
||||
getRepairHistory: async (equipmentId) => {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
wir.*,
|
||||
irc.category_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 workplaces w ON wir.workplace_id = w.workplace_id
|
||||
WHERE wir.equipment_id = ?
|
||||
ORDER BY wir.created_at DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY wir.created_at DESC`,
|
||||
[equipmentId]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// GET REPAIR CATEGORIES - 설비 수리 항목 목록 조회
|
||||
getRepairCategories: async (callback) => {
|
||||
try {
|
||||
getRepairCategories: async () => {
|
||||
const db = await getDb();
|
||||
|
||||
// 설비 수리 카테고리의 항목들 조회
|
||||
const query = `
|
||||
SELECT iri.item_id, iri.item_name, iri.description, iri.severity
|
||||
const [rows] = await db.query(
|
||||
`SELECT iri.item_id, iri.item_name, iri.description, iri.severity
|
||||
FROM issue_report_items iri
|
||||
INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE irc.category_name = '설비 수리' AND iri.is_active = 1
|
||||
ORDER BY iri.display_order ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
ORDER BY iri.display_order ASC`
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// ADD REPAIR CATEGORY - 새 수리 항목 추가
|
||||
addRepairCategory: async (itemName, callback) => {
|
||||
try {
|
||||
addRepairCategory: async (itemName) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 설비 수리 카테고리 ID 조회
|
||||
const [categories] = await db.query(
|
||||
"SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리'"
|
||||
);
|
||||
|
||||
if (categories.length === 0) {
|
||||
return callback(new Error('설비 수리 카테고리가 없습니다.'));
|
||||
throw new Error('설비 수리 카테고리가 없습니다.');
|
||||
}
|
||||
|
||||
const categoryId = categories[0].category_id;
|
||||
|
||||
// 중복 확인
|
||||
const [existing] = await db.query(
|
||||
'SELECT item_id FROM issue_report_items WHERE category_id = ? AND item_name = ?',
|
||||
[categoryId, itemName]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
// 이미 존재하면 해당 ID 반환
|
||||
return callback(null, { item_id: existing[0].item_id, item_name: itemName, isNew: false });
|
||||
return { item_id: existing[0].item_id, item_name: itemName, isNew: false };
|
||||
}
|
||||
|
||||
// 다음 display_order 구하기
|
||||
const [maxOrder] = await db.query(
|
||||
'SELECT MAX(display_order) as max_order FROM issue_report_items WHERE category_id = ?',
|
||||
[categoryId]
|
||||
);
|
||||
const nextOrder = (maxOrder[0].max_order || 0) + 1;
|
||||
|
||||
// 새 항목 추가
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO issue_report_items (category_id, item_name, display_order, is_active)
|
||||
VALUES (?, ?, ?, 1)`,
|
||||
[categoryId, itemName, nextOrder]
|
||||
);
|
||||
|
||||
callback(null, { item_id: result.insertId, item_name: itemName, isNew: true });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
return { item_id: result.insertId, item_name: itemName, isNew: true };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -6,11 +6,10 @@ const TbmTransferModel = {
|
||||
* 작업자 이동 실행 (보내기/빼오기)
|
||||
* 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT
|
||||
*/
|
||||
createTransfer: async (transferData, callback) => {
|
||||
let conn;
|
||||
try {
|
||||
async createTransfer(transferData) {
|
||||
const db = await getDb();
|
||||
conn = await db.getConnection();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
const {
|
||||
@@ -27,8 +26,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (sourceRows.length === 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' });
|
||||
return { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' };
|
||||
}
|
||||
|
||||
const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
|
||||
@@ -36,8 +34,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (newSourceHours < 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' });
|
||||
return { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' };
|
||||
}
|
||||
|
||||
await conn.query(
|
||||
@@ -52,14 +49,12 @@ const TbmTransferModel = {
|
||||
);
|
||||
|
||||
if (destRows.length > 0) {
|
||||
// 이미 있으면 시간만 추가
|
||||
const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
|
||||
await conn.query(
|
||||
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
|
||||
[existingHours + parseFloat(hours), dest_session_id, worker_id]
|
||||
);
|
||||
} else {
|
||||
// 새로 INSERT
|
||||
await conn.query(
|
||||
`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)
|
||||
@@ -89,7 +84,6 @@ const TbmTransferModel = {
|
||||
const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0;
|
||||
|
||||
await conn.commit();
|
||||
conn.release();
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
@@ -101,24 +95,22 @@ const TbmTransferModel = {
|
||||
result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`;
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (conn) {
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 이동 취소 (원복)
|
||||
*/
|
||||
cancelTransfer: async (transferId, callback) => {
|
||||
let conn;
|
||||
try {
|
||||
async cancelTransfer(transferId) {
|
||||
const db = await getDb();
|
||||
conn = await db.getConnection();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
// 1. 이동 로그 조회
|
||||
@@ -129,8 +121,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (transfers.length === 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '이동 기록을 찾을 수 없습니다.' });
|
||||
return { success: false, message: '이동 기록을 찾을 수 없습니다.' };
|
||||
}
|
||||
|
||||
const t = transfers[0];
|
||||
@@ -146,7 +137,6 @@ const TbmTransferModel = {
|
||||
const newDestHours = destHours - parseFloat(t.hours);
|
||||
|
||||
if (newDestHours <= 0) {
|
||||
// 삭제
|
||||
await conn.query(
|
||||
'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?',
|
||||
[t.dest_session_id, t.worker_id]
|
||||
@@ -168,7 +158,6 @@ const TbmTransferModel = {
|
||||
if (sourceRows.length > 0) {
|
||||
const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
|
||||
const restoredHours = sourceHours + parseFloat(t.hours);
|
||||
// 8이면 NULL로 복원 (종일)
|
||||
await conn.query(
|
||||
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND 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.commit();
|
||||
conn.release();
|
||||
|
||||
callback(null, { success: true });
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
if (conn) {
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 이동 내역 조회
|
||||
*/
|
||||
getTransfersByDate: async (date, callback) => {
|
||||
try {
|
||||
async getTransfersByDate(date) {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
@@ -216,17 +201,13 @@ const TbmTransferModel = {
|
||||
ORDER BY t.created_at DESC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 전 작업자 배정 현황 조회
|
||||
*/
|
||||
getWorkerAssignmentsByDate: async (date, callback) => {
|
||||
try {
|
||||
async getWorkerAssignmentsByDate(date) {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 해당 날짜의 모든 배정 가져오기
|
||||
@@ -279,15 +260,11 @@ const TbmTransferModel = {
|
||||
}
|
||||
});
|
||||
|
||||
// available 판단
|
||||
Object.values(workerMap).forEach(w => {
|
||||
w.available = w.total_hours < 8;
|
||||
});
|
||||
|
||||
callback(null, Object.values(workerMap));
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return Object.values(workerMap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,10 +9,9 @@ const vacationBalanceModel = {
|
||||
/**
|
||||
* 특정 작업자의 모든 휴가 잔액 조회 (특정 연도)
|
||||
*/
|
||||
async getByWorkerAndYear(workerId, year, callback) {
|
||||
try {
|
||||
async getByWorkerAndYear(workerId, year) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
@@ -23,21 +22,16 @@ const vacationBalanceModel = {
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ? AND vbd.year = ?
|
||||
ORDER BY vt.priority ASC, vt.type_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [workerId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 특정 휴가 유형 잔액 조회
|
||||
*/
|
||||
async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) {
|
||||
try {
|
||||
async getByWorkerTypeYear(workerId, vacationTypeId, year) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
@@ -47,22 +41,16 @@ const vacationBalanceModel = {
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.vacation_type_id = ?
|
||||
AND vbd.year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [workerId, vacationTypeId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 모든 작업자의 휴가 잔액 조회 (특정 연도)
|
||||
* - 연간 연차 현황 차트용
|
||||
*/
|
||||
async getAllByYear(year, callback) {
|
||||
try {
|
||||
async getAllByYear(year) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
@@ -76,108 +64,75 @@ const vacationBalanceModel = {
|
||||
WHERE vbd.year = ?
|
||||
AND w.employment_status = 'employed'
|
||||
ORDER BY w.worker_name ASC, vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 생성
|
||||
*/
|
||||
async create(balanceData, callback) {
|
||||
try {
|
||||
async create(balanceData) {
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_balance_details SET ?`;
|
||||
const [rows] = await db.query(query, balanceData);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`INSERT INTO vacation_balance_details SET ?`, balanceData);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 수정
|
||||
*/
|
||||
async update(id, updateData, callback) {
|
||||
try {
|
||||
async update(id, updateData) {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_balance_details SET ? WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [updateData, id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`UPDATE vacation_balance_details SET ? WHERE id = ?`, [updateData, id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 삭제
|
||||
*/
|
||||
async delete(id, callback) {
|
||||
try {
|
||||
async delete(id) {
|
||||
const db = await getDb();
|
||||
const query = `DELETE FROM vacation_balance_details WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`DELETE FROM vacation_balance_details WHERE id = ?`, [id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 업데이트 (차감)
|
||||
* - 휴가 신청 승인 시 호출
|
||||
*/
|
||||
async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) {
|
||||
try {
|
||||
async deductDays(workerId, vacationTypeId, year, daysToDeduct) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [result] = await db.query(`
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = used_days + ?,
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToDeduct, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [daysToDeduct, workerId, vacationTypeId, year]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 복구 (취소)
|
||||
* - 휴가 신청 취소/거부 시 호출
|
||||
*/
|
||||
async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) {
|
||||
try {
|
||||
async restoreDays(workerId, vacationTypeId, year, daysToRestore) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [result] = await db.query(`
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = GREATEST(0, used_days - ?),
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToRestore, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [daysToRestore, workerId, vacationTypeId, year]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 사용 가능한 휴가 일수 확인
|
||||
* - 우선순위가 높은 순서대로 차감 가능 여부 확인
|
||||
*/
|
||||
async getAvailableVacationDays(workerId, year, callback) {
|
||||
try {
|
||||
async getAvailableVacationDays(workerId, year) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.id,
|
||||
vbd.vacation_type_id,
|
||||
@@ -193,26 +148,19 @@ const vacationBalanceModel = {
|
||||
AND vbd.year = ?
|
||||
AND vbd.remaining_days > 0
|
||||
ORDER BY vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [workerId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자별 휴가 잔액 일괄 생성 (연도별)
|
||||
* - 매년 초 또는 입사 시 사용
|
||||
*/
|
||||
async bulkCreate(balances, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
async bulkCreate(balances) {
|
||||
if (!balances || balances.length === 0) {
|
||||
return callback(new Error('생성할 휴가 잔액 데이터가 없습니다'));
|
||||
throw new Error('생성할 휴가 잔액 데이터가 없습니다');
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_balance_details
|
||||
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
|
||||
VALUES ?`;
|
||||
@@ -227,33 +175,24 @@ const vacationBalanceModel = {
|
||||
b.created_by
|
||||
]);
|
||||
|
||||
const [rows] = await db.query(query, [values]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(query, [values]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 근속년수 기반 연차 일수 계산 (한국 근로기준법)
|
||||
* @param {Date} hireDate - 입사일
|
||||
* @param {number} targetYear - 대상 연도
|
||||
* @returns {number} - 부여받을 연차 일수
|
||||
*/
|
||||
calculateAnnualLeaveDays(hireDate, targetYear) {
|
||||
const hire = new Date(hireDate);
|
||||
const targetDate = new Date(targetYear, 0, 1);
|
||||
|
||||
// 근속 월수 계산
|
||||
const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12
|
||||
+ (targetDate.getMonth() - hire.getMonth());
|
||||
|
||||
// 1년 미만: 월 1일
|
||||
if (monthsDiff < 12) {
|
||||
return Math.floor(monthsDiff);
|
||||
}
|
||||
|
||||
// 1년 이상: 15일 기본 + 2년마다 1일 추가 (최대 25일)
|
||||
const yearsWorked = Math.floor(monthsDiff / 12);
|
||||
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) {
|
||||
const db = await getDb();
|
||||
|
||||
// 우선순위순으로 잔여 일수가 있는 잔액 조회
|
||||
const [balances] = await db.query(`
|
||||
SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days,
|
||||
(vbd.total_days - vbd.used_days) as remaining_days,
|
||||
@@ -284,7 +217,6 @@ const vacationBalanceModel = {
|
||||
`, [workerId, year]);
|
||||
|
||||
if (balances.length === 0) {
|
||||
// 잔액이 없어도 일단 기록은 저장 (경고만)
|
||||
console.warn(`[VacationBalance] 작업자 ${workerId}의 ${year}년 휴가 잔액이 없습니다`);
|
||||
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) {
|
||||
const db = await getDb();
|
||||
|
||||
// 우선순위 역순으로 사용 일수가 있는 잔액 조회 (나중에 차감된 것부터 복구)
|
||||
const [balances] = await db.query(`
|
||||
SELECT vbd.id, vbd.vacation_type_id, vbd.used_days,
|
||||
vt.type_code, vt.type_name, vt.priority
|
||||
@@ -375,10 +302,9 @@ const vacationBalanceModel = {
|
||||
/**
|
||||
* 특정 ID로 휴가 잔액 조회
|
||||
*/
|
||||
async getById(id, callback) {
|
||||
try {
|
||||
async getById(id) {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
@@ -388,12 +314,8 @@ const vacationBalanceModel = {
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.id = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`, [id]);
|
||||
return rows;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,123 +9,97 @@ const vacationTypeModel = {
|
||||
/**
|
||||
* 모든 활성 휴가 유형 조회 (우선순위 순서대로)
|
||||
*/
|
||||
async getAll(callback) {
|
||||
try {
|
||||
async getAll() {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_active = 1
|
||||
ORDER BY priority ASC, id ASC
|
||||
`;
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 시스템 기본 휴가 유형만 조회
|
||||
*/
|
||||
async getSystemTypes(callback) {
|
||||
try {
|
||||
async getSystemTypes() {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
const [rows] = await db.query(`
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_system = 1 AND is_active = 1
|
||||
ORDER BY priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특별 휴가 유형만 조회
|
||||
*/
|
||||
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로 휴가 유형 조회
|
||||
*/
|
||||
async getById(id, callback) {
|
||||
try {
|
||||
async getById(id) {
|
||||
const db = await getDb();
|
||||
const query = `SELECT * FROM vacation_types WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [rows] = await db.query(`SELECT * FROM vacation_types WHERE id = ?`, [id]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 코드로 조회
|
||||
*/
|
||||
async getByCode(code, callback) {
|
||||
try {
|
||||
async getByCode(code) {
|
||||
const db = await getDb();
|
||||
const query = `SELECT * FROM vacation_types WHERE type_code = ?`;
|
||||
const [rows] = await db.query(query, [code]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [rows] = await db.query(`SELECT * FROM vacation_types WHERE type_code = ?`, [code]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 생성
|
||||
*/
|
||||
async create(typeData, callback) {
|
||||
try {
|
||||
async create(typeData) {
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_types SET ?`;
|
||||
const [result] = await db.query(query, typeData);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`INSERT INTO vacation_types SET ?`, typeData);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 수정
|
||||
*/
|
||||
async update(id, updateData, callback) {
|
||||
try {
|
||||
async update(id, updateData) {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET ? WHERE id = ?`;
|
||||
const [result] = await db.query(query, [updateData, id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`UPDATE vacation_types SET ? WHERE id = ?`, [updateData, id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 삭제 (논리적 삭제 - is_active = 0)
|
||||
*/
|
||||
async delete(id, callback) {
|
||||
try {
|
||||
async delete(id) {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`;
|
||||
const [result] = await db.query(query, [id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`, [id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 우선순위 업데이트
|
||||
*/
|
||||
async updatePriority(id, priority, callback) {
|
||||
try {
|
||||
async updatePriority(id, priority) {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`;
|
||||
const [result] = await db.query(query, [priority, id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
const [result] = await db.query(`UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`, [priority, id]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 출입 신청 관리 ====================
|
||||
|
||||
/**
|
||||
* 출입 신청 생성
|
||||
*/
|
||||
const createVisitRequest = async (requestData, callback) => {
|
||||
try {
|
||||
const createVisitRequest = async (requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
requester_id,
|
||||
@@ -29,17 +25,10 @@ const createVisitRequest = async (requestData, callback) => {
|
||||
visit_date, visit_time, purpose_id, notes]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 목록 조회 (필터 옵션 포함)
|
||||
*/
|
||||
const getAllVisitRequests = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getAllVisitRequests = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -63,27 +52,22 @@ const getAllVisitRequests = async (filters = {}, callback) => {
|
||||
|
||||
const params = [];
|
||||
|
||||
// 필터 적용
|
||||
if (filters.status) {
|
||||
query += ` AND vr.status = ?`;
|
||||
params.push(filters.status);
|
||||
}
|
||||
|
||||
if (filters.visit_date) {
|
||||
query += ` AND vr.visit_date = ?`;
|
||||
params.push(filters.visit_date);
|
||||
}
|
||||
|
||||
if (filters.start_date && filters.end_date) {
|
||||
query += ` AND vr.visit_date BETWEEN ? AND ?`;
|
||||
params.push(filters.start_date, filters.end_date);
|
||||
}
|
||||
|
||||
if (filters.requester_id) {
|
||||
query += ` AND vr.requester_id = ?`;
|
||||
params.push(filters.requester_id);
|
||||
}
|
||||
|
||||
if (filters.category_id) {
|
||||
query += ` AND vr.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`;
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상세 조회
|
||||
*/
|
||||
const getVisitRequestById = async (requestId, callback) => {
|
||||
try {
|
||||
const getVisitRequestById = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
@@ -124,28 +101,14 @@ const getVisitRequestById = async (requestId, callback) => {
|
||||
WHERE vr.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 수정
|
||||
*/
|
||||
const updateVisitRequest = async (requestId, requestData, callback) => {
|
||||
try {
|
||||
const updateVisitRequest = async (requestId, requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
visitor_company,
|
||||
visitor_count,
|
||||
category_id,
|
||||
workplace_id,
|
||||
visit_date,
|
||||
visit_time,
|
||||
purpose_id,
|
||||
notes
|
||||
visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes
|
||||
} = requestData;
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -156,34 +119,19 @@ const updateVisitRequest = async (requestId, requestData, callback) => {
|
||||
[visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 삭제
|
||||
*/
|
||||
const deleteVisitRequest = async (requestId, callback) => {
|
||||
try {
|
||||
const deleteVisitRequest = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 승인
|
||||
*/
|
||||
const approveVisitRequest = async (requestId, approvedBy, callback) => {
|
||||
try {
|
||||
const approveVisitRequest = async (requestId, approvedBy) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
@@ -191,18 +139,10 @@ const approveVisitRequest = async (requestId, approvedBy, callback) => {
|
||||
WHERE request_id = ?`,
|
||||
[approvedBy, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 반려
|
||||
*/
|
||||
const rejectVisitRequest = async (requestId, rejectionData, callback) => {
|
||||
try {
|
||||
const rejectVisitRequest = async (requestId, rejectionData) => {
|
||||
const db = await getDb();
|
||||
const { approved_by, rejection_reason } = rejectionData;
|
||||
|
||||
@@ -213,18 +153,10 @@ const rejectVisitRequest = async (requestId, rejectionData, callback) => {
|
||||
WHERE request_id = ?`,
|
||||
[approved_by, rejection_reason, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상태 변경
|
||||
*/
|
||||
const updateVisitRequestStatus = async (requestId, status, callback) => {
|
||||
try {
|
||||
const updateVisitRequestStatus = async (requestId, status) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
@@ -232,37 +164,22 @@ const updateVisitRequestStatus = async (requestId, status, callback) => {
|
||||
WHERE request_id = ?`,
|
||||
[status, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 방문 목적 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 방문 목적 조회
|
||||
*/
|
||||
const getAllVisitPurposes = async (callback) => {
|
||||
try {
|
||||
const getAllVisitPurposes = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 방문 목적만 조회
|
||||
*/
|
||||
const getActiveVisitPurposes = async (callback) => {
|
||||
try {
|
||||
const getActiveVisitPurposes = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
@@ -270,17 +187,10 @@ const getActiveVisitPurposes = async (callback) => {
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 추가
|
||||
*/
|
||||
const createVisitPurpose = async (purposeData, callback) => {
|
||||
try {
|
||||
const createVisitPurpose = async (purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order = 0, is_active = true } = purposeData;
|
||||
|
||||
@@ -289,18 +199,10 @@ const createVisitPurpose = async (purposeData, callback) => {
|
||||
VALUES (?, ?, ?)`,
|
||||
[purpose_name, display_order, is_active]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 수정
|
||||
*/
|
||||
const updateVisitPurpose = async (purposeId, purposeData, callback) => {
|
||||
try {
|
||||
const updateVisitPurpose = async (purposeId, purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order, is_active } = purposeData;
|
||||
|
||||
@@ -310,44 +212,25 @@ const updateVisitPurpose = async (purposeId, purposeData, callback) => {
|
||||
WHERE purpose_id = ?`,
|
||||
[purpose_name, display_order, is_active, purposeId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 삭제
|
||||
*/
|
||||
const deleteVisitPurpose = async (purposeId, callback) => {
|
||||
try {
|
||||
const deleteVisitPurpose = async (purposeId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
|
||||
[purposeId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 안전교육 기록 관리 ====================
|
||||
|
||||
/**
|
||||
* 안전교육 기록 생성
|
||||
*/
|
||||
const createTrainingRecord = async (trainingData, callback) => {
|
||||
try {
|
||||
const createTrainingRecord = async (trainingData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
request_id,
|
||||
trainer_id,
|
||||
training_date,
|
||||
training_start_time,
|
||||
training_end_time = null,
|
||||
training_topics = null
|
||||
request_id, trainer_id, training_date,
|
||||
training_start_time, training_end_time = null, training_topics = null
|
||||
} = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -356,18 +239,10 @@ const createTrainingRecord = async (trainingData, callback) => {
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 출입 신청의 안전교육 기록 조회
|
||||
*/
|
||||
const getTrainingRecordByRequestId = async (requestId, callback) => {
|
||||
try {
|
||||
const getTrainingRecordByRequestId = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
@@ -380,25 +255,12 @@ const getTrainingRecordByRequestId = async (requestId, callback) => {
|
||||
WHERE str.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 기록 수정
|
||||
*/
|
||||
const updateTrainingRecord = async (trainingId, trainingData, callback) => {
|
||||
try {
|
||||
const updateTrainingRecord = async (trainingId, trainingData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
training_date,
|
||||
training_start_time,
|
||||
training_end_time,
|
||||
training_topics
|
||||
} = trainingData;
|
||||
const { training_date, training_start_time, training_end_time, training_topics } = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
@@ -407,18 +269,10 @@ const updateTrainingRecord = async (trainingId, trainingData, callback) => {
|
||||
WHERE training_id = ?`,
|
||||
[training_date, training_start_time, training_end_time, training_topics, trainingId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 완료 (서명 포함)
|
||||
*/
|
||||
const completeTraining = async (trainingId, signatureData, callback) => {
|
||||
try {
|
||||
const completeTraining = async (trainingId, signatureData) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
@@ -426,18 +280,10 @@ const completeTraining = async (trainingId, signatureData, callback) => {
|
||||
WHERE training_id = ?`,
|
||||
[signatureData, trainingId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 목록 조회 (날짜별 필터)
|
||||
*/
|
||||
const getTrainingRecords = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getTrainingRecords = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -458,12 +304,10 @@ const getTrainingRecords = async (filters = {}, callback) => {
|
||||
query += ` AND str.training_date = ?`;
|
||||
params.push(filters.training_date);
|
||||
}
|
||||
|
||||
if (filters.start_date && filters.end_date) {
|
||||
query += ` AND str.training_date BETWEEN ? AND ?`;
|
||||
params.push(filters.start_date, filters.end_date);
|
||||
}
|
||||
|
||||
if (filters.trainer_id) {
|
||||
query += ` AND str.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`;
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// 출입 신청
|
||||
createVisitRequest,
|
||||
getAllVisitRequests,
|
||||
getVisitRequestById,
|
||||
@@ -488,15 +328,11 @@ module.exports = {
|
||||
approveVisitRequest,
|
||||
rejectVisitRequest,
|
||||
updateVisitRequestStatus,
|
||||
|
||||
// 방문 목적
|
||||
getAllVisitPurposes,
|
||||
getActiveVisitPurposes,
|
||||
createVisitPurpose,
|
||||
updateVisitPurpose,
|
||||
deleteVisitPurpose,
|
||||
|
||||
// 안전교육
|
||||
createTrainingRecord,
|
||||
getTrainingRecordByRequestId,
|
||||
updateTrainingRecord,
|
||||
|
||||
@@ -7,28 +7,17 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 신고 카테고리 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 신고 카테고리 조회
|
||||
*/
|
||||
const getAllCategories = async (callback) => {
|
||||
try {
|
||||
const getAllCategories = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_type, category_name, description, display_order, is_active, created_at
|
||||
FROM issue_report_categories
|
||||
ORDER BY category_type, display_order, category_id`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 타입별 활성 카테고리 조회 (nonconformity/safety)
|
||||
*/
|
||||
const getCategoriesByType = async (categoryType, callback) => {
|
||||
try {
|
||||
const getCategoriesByType = async (categoryType) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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`,
|
||||
[categoryType]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
const createCategory = async (categoryData, callback) => {
|
||||
try {
|
||||
const createCategory = async (categoryData) => {
|
||||
const db = await getDb();
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
const updateCategory = async (categoryId, categoryData, callback) => {
|
||||
try {
|
||||
const updateCategory = async (categoryId, categoryData) => {
|
||||
const db = await getDb();
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
const deleteCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const deleteCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM issue_report_categories WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 사전 정의 신고 항목 관리 ====================
|
||||
|
||||
/**
|
||||
* 카테고리별 활성 항목 조회
|
||||
*/
|
||||
const getItemsByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const getItemsByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 항목 조회 (관리용)
|
||||
*/
|
||||
const getAllItems = async (callback) => {
|
||||
try {
|
||||
const getAllItems = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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
|
||||
ORDER BY irc.category_type, irc.display_order, iri.display_order, iri.item_id`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 생성
|
||||
*/
|
||||
const createItem = async (itemData, callback) => {
|
||||
try {
|
||||
const createItem = async (itemData) => {
|
||||
const db = await getDb();
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 수정
|
||||
*/
|
||||
const updateItem = async (itemId, itemData, callback) => {
|
||||
try {
|
||||
const updateItem = async (itemId, itemData) => {
|
||||
const db = await getDb();
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 삭제
|
||||
*/
|
||||
const deleteItem = async (itemId, callback) => {
|
||||
try {
|
||||
const deleteItem = async (itemId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM issue_report_items WHERE item_id = ?`,
|
||||
[itemId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 문제 신고 관리 ====================
|
||||
@@ -203,11 +133,7 @@ const deleteItem = async (itemId, callback) => {
|
||||
// 한국 시간 유틸리티 import
|
||||
const { getKoreaDatetime } = require('../utils/dateUtils');
|
||||
|
||||
/**
|
||||
* 신고 생성
|
||||
*/
|
||||
const createReport = async (reportData, callback) => {
|
||||
try {
|
||||
const createReport = async (reportData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
reporter_id,
|
||||
@@ -226,7 +152,6 @@ const createReport = async (reportData, callback) => {
|
||||
photo_path5 = null
|
||||
} = reportData;
|
||||
|
||||
// 한국 시간 기준으로 신고 일시 설정
|
||||
const reportDate = getKoreaDatetime();
|
||||
|
||||
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]
|
||||
);
|
||||
|
||||
// 상태 변경 로그 기록
|
||||
await db.query(
|
||||
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
|
||||
VALUES (?, NULL, 'reported', ?)`,
|
||||
[result.insertId, reporter_id]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 목록 조회 (필터 옵션 포함)
|
||||
*/
|
||||
const getAllReports = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getAllReports = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -287,7 +204,6 @@ const getAllReports = async (filters = {}, callback) => {
|
||||
|
||||
const params = [];
|
||||
|
||||
// 필터 적용
|
||||
if (filters.status) {
|
||||
query += ` AND wir.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`;
|
||||
|
||||
// 페이지네이션
|
||||
if (filters.limit) {
|
||||
query += ` LIMIT ?`;
|
||||
params.push(parseInt(filters.limit));
|
||||
@@ -348,17 +263,10 @@ const getAllReports = async (filters = {}, callback) => {
|
||||
}
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 상세 조회
|
||||
*/
|
||||
const getReportById = async (reportId, callback) => {
|
||||
try {
|
||||
const getReportById = async (reportId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
@@ -393,32 +301,23 @@ const getReportById = async (reportId, callback) => {
|
||||
[reportId]
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 수정
|
||||
*/
|
||||
const updateReport = async (reportId, reportData, userId, callback) => {
|
||||
try {
|
||||
const updateReport = async (reportId, reportData, userId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 기존 데이터 조회
|
||||
const [existing] = await db.query(
|
||||
`SELECT * FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const current = existing[0];
|
||||
|
||||
// 수정 이력 생성
|
||||
const modifications = [];
|
||||
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 newHistory = [...existingHistory, ...modifications];
|
||||
|
||||
@@ -474,20 +372,12 @@ const updateReport = async (reportId, reportData, userId, callback) => {
|
||||
JSON.stringify(newHistory), reportId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 삭제
|
||||
*/
|
||||
const deleteReport = async (reportId, callback) => {
|
||||
try {
|
||||
const deleteReport = async (reportId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 먼저 사진 경로 조회 (삭제용)
|
||||
const [photos] = await db.query(
|
||||
`SELECT photo_path1, photo_path2, photo_path3, photo_path4, photo_path5,
|
||||
resolution_photo_path1, resolution_photo_path2
|
||||
@@ -500,34 +390,25 @@ const deleteReport = async (reportId, callback) => {
|
||||
[reportId]
|
||||
);
|
||||
|
||||
// 삭제할 사진 경로 반환
|
||||
callback(null, { result, photos: photos[0] });
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return { result, photos: photos[0] };
|
||||
};
|
||||
|
||||
// ==================== 상태 관리 ====================
|
||||
|
||||
/**
|
||||
* 신고 접수 (reported → received)
|
||||
*/
|
||||
const receiveReport = async (reportId, userId, callback) => {
|
||||
try {
|
||||
const receiveReport = async (reportId, userId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 현재 상태 확인
|
||||
const [current] = await db.query(
|
||||
`SELECT status FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (current.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (current[0].status !== 'reported') {
|
||||
return callback(new Error('접수 대기 상태가 아닙니다.'));
|
||||
throw new Error('접수 대기 상태가 아닙니다.');
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -537,41 +418,31 @@ const receiveReport = async (reportId, userId, callback) => {
|
||||
[reportId]
|
||||
);
|
||||
|
||||
// 상태 변경 로그
|
||||
await db.query(
|
||||
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
|
||||
VALUES (?, 'reported', 'received', ?)`,
|
||||
[reportId, userId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 담당자 배정
|
||||
*/
|
||||
const assignReport = async (reportId, assignData, callback) => {
|
||||
try {
|
||||
const assignReport = async (reportId, assignData) => {
|
||||
const db = await getDb();
|
||||
const { assigned_department, assigned_user_id, assigned_by } = assignData;
|
||||
|
||||
// 현재 상태 확인
|
||||
const [current] = await db.query(
|
||||
`SELECT status FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (current.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 접수 상태 이상이어야 배정 가능
|
||||
const validStatuses = ['received', 'in_progress'];
|
||||
if (!validStatuses.includes(current[0].status)) {
|
||||
return callback(new Error('접수된 상태에서만 담당자 배정이 가능합니다.'));
|
||||
throw new Error('접수된 상태에서만 담당자 배정이 가능합니다.');
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -582,31 +453,23 @@ const assignReport = async (reportId, assignData, callback) => {
|
||||
[assigned_department, assigned_user_id, assigned_by, reportId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 시작 (received → in_progress)
|
||||
*/
|
||||
const startProcessing = async (reportId, userId, callback) => {
|
||||
try {
|
||||
const startProcessing = async (reportId, userId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 현재 상태 확인
|
||||
const [current] = await db.query(
|
||||
`SELECT status FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (current.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (current[0].status !== 'received') {
|
||||
return callback(new Error('접수된 상태에서만 처리를 시작할 수 있습니다.'));
|
||||
throw new Error('접수된 상태에서만 처리를 시작할 수 있습니다.');
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -616,39 +479,30 @@ const startProcessing = async (reportId, userId, callback) => {
|
||||
[reportId]
|
||||
);
|
||||
|
||||
// 상태 변경 로그
|
||||
await db.query(
|
||||
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
|
||||
VALUES (?, 'received', 'in_progress', ?)`,
|
||||
[reportId, userId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 완료 (in_progress → completed)
|
||||
*/
|
||||
const completeReport = async (reportId, completionData, callback) => {
|
||||
try {
|
||||
const completeReport = async (reportId, completionData) => {
|
||||
const db = await getDb();
|
||||
const { resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by } = completionData;
|
||||
|
||||
// 현재 상태 확인
|
||||
const [current] = await db.query(
|
||||
`SELECT status FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (current.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (current[0].status !== 'in_progress') {
|
||||
return callback(new Error('처리 중 상태에서만 완료할 수 있습니다.'));
|
||||
throw new Error('처리 중 상태에서만 완료할 수 있습니다.');
|
||||
}
|
||||
|
||||
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]
|
||||
);
|
||||
|
||||
// 상태 변경 로그
|
||||
await db.query(
|
||||
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by, change_reason)
|
||||
VALUES (?, 'in_progress', 'completed', ?, ?)`,
|
||||
[reportId, resolved_by, resolution_notes]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 종료 (completed → closed)
|
||||
*/
|
||||
const closeReport = async (reportId, userId, callback) => {
|
||||
try {
|
||||
const closeReport = async (reportId, userId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 현재 상태 확인
|
||||
const [current] = await db.query(
|
||||
`SELECT status FROM work_issue_reports WHERE report_id = ?`,
|
||||
[reportId]
|
||||
);
|
||||
|
||||
if (current.length === 0) {
|
||||
return callback(new Error('신고를 찾을 수 없습니다.'));
|
||||
throw new Error('신고를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (current[0].status !== 'completed') {
|
||||
return callback(new Error('완료된 상태에서만 종료할 수 있습니다.'));
|
||||
throw new Error('완료된 상태에서만 종료할 수 있습니다.');
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
@@ -701,24 +546,16 @@ const closeReport = async (reportId, userId, callback) => {
|
||||
[reportId]
|
||||
);
|
||||
|
||||
// 상태 변경 로그
|
||||
await db.query(
|
||||
`INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by)
|
||||
VALUES (?, 'completed', 'closed', ?)`,
|
||||
[reportId, userId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 상태 변경 이력 조회
|
||||
*/
|
||||
const getStatusLogs = async (reportId, callback) => {
|
||||
try {
|
||||
const getStatusLogs = async (reportId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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`,
|
||||
[reportId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
// ==================== 통계 ====================
|
||||
|
||||
/**
|
||||
* 신고 통계 요약
|
||||
*/
|
||||
const getStatsSummary = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getStatsSummary = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
|
||||
let whereClause = '1=1';
|
||||
@@ -771,17 +601,10 @@ const getStatsSummary = async (filters = {}, callback) => {
|
||||
params
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리별 통계
|
||||
*/
|
||||
const getStatsByCategory = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getStatsByCategory = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
|
||||
let whereClause = '1=1';
|
||||
@@ -804,17 +627,10 @@ const getStatsByCategory = async (filters = {}, callback) => {
|
||||
params
|
||||
);
|
||||
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장별 통계
|
||||
*/
|
||||
const getStatsByWorkplace = async (filters = {}, callback) => {
|
||||
try {
|
||||
const getStatsByWorkplace = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
|
||||
let whereClause = 'wir.workplace_id IS NOT NULL';
|
||||
@@ -844,10 +660,7 @@ const getStatsByWorkplace = async (filters = {}, callback) => {
|
||||
params
|
||||
);
|
||||
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -3,7 +3,7 @@ const { getDb } = require('../dbPool');
|
||||
/**
|
||||
* 1. 여러 건 등록 (트랜잭션 사용)
|
||||
*/
|
||||
const createBatch = async (reports, callback) => {
|
||||
const createBatch = async (reports) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -30,10 +30,9 @@ const createBatch = async (reports, callback) => {
|
||||
}
|
||||
|
||||
await conn.commit();
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
callback(err);
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
@@ -42,8 +41,7 @@ const createBatch = async (reports, callback) => {
|
||||
/**
|
||||
* 2. 단일 등록
|
||||
*/
|
||||
const create = async (report, callback) => {
|
||||
try {
|
||||
const create = async (report) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
@@ -66,21 +64,17 @@ const create = async (report, callback) => {
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* 3. 날짜별 조회
|
||||
*/
|
||||
const getAllByDate = async (date, callback) => {
|
||||
try {
|
||||
const getAllByDate = async (date) => {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
wr.worker_id, -- 이 줄을 추가했습니다
|
||||
wr.worker_id,
|
||||
wr.id,
|
||||
wr.\`date\`,
|
||||
w.worker_name,
|
||||
@@ -97,17 +91,13 @@ const create = async (report, callback) => {
|
||||
ORDER BY w.worker_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 기간 조회
|
||||
*/
|
||||
const getByRange = async (start, end, callback) => {
|
||||
try {
|
||||
const getByRange = async (start, end) => {
|
||||
const db = await getDb();
|
||||
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
|
||||
@@ -115,33 +105,25 @@ const getByRange = async (start, end, callback) => {
|
||||
ORDER BY \`date\` ASC`,
|
||||
[start, end]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. ID로 조회
|
||||
*/
|
||||
const getById = async (id, callback) => {
|
||||
try {
|
||||
const getById = async (id) => {
|
||||
const db = await getDb();
|
||||
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 = ?`,
|
||||
[id]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 6. 수정
|
||||
*/
|
||||
const update = async (id, report, callback) => {
|
||||
try {
|
||||
const update = async (id, report) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
@@ -172,45 +154,33 @@ const update = async (id, report, callback) => {
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 7. 삭제
|
||||
*/
|
||||
const remove = async (id, callback) => {
|
||||
try {
|
||||
const remove = async (id) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM WorkReports WHERE id = ?`,
|
||||
[id]
|
||||
);
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
callback(new Error(err.message || String(err)));
|
||||
}
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 8. 중복 확인
|
||||
*/
|
||||
const existsByDateAndWorker = async (date, worker_id, callback) => {
|
||||
try {
|
||||
const existsByDateAndWorker = async (date, worker_id) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`,
|
||||
[date, worker_id]
|
||||
);
|
||||
callback(null, rows.length > 0);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows.length > 0;
|
||||
};
|
||||
|
||||
// ✅ 내보내기
|
||||
module.exports = {
|
||||
create,
|
||||
createBatch,
|
||||
|
||||
@@ -2,11 +2,7 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 카테고리(공장) 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
const createCategory = async (category, callback) => {
|
||||
try {
|
||||
const createCategory = async (category) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
@@ -22,34 +18,20 @@ const createCategory = async (category, callback) => {
|
||||
[category_name, description, display_order, is_active]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 카테고리 조회
|
||||
*/
|
||||
const getAllCategories = async (callback) => {
|
||||
try {
|
||||
const getAllCategories = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
ORDER BY display_order ASC, category_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 카테고리만 조회
|
||||
*/
|
||||
const getActiveCategories = async (callback) => {
|
||||
try {
|
||||
const getActiveCategories = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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
|
||||
ORDER BY display_order ASC, category_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* ID로 카테고리 조회
|
||||
*/
|
||||
const getCategoryById = async (categoryId, callback) => {
|
||||
try {
|
||||
const getCategoryById = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`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 = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
const updateCategory = async (categoryId, category, callback) => {
|
||||
try {
|
||||
const updateCategory = async (categoryId, category) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
@@ -102,35 +70,21 @@ const updateCategory = async (categoryId, category, callback) => {
|
||||
[category_name, description, display_order, is_active, layout_image, categoryId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
const deleteCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const deleteCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_categories WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 작업장 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 생성
|
||||
*/
|
||||
const createWorkplace = async (workplace, callback) => {
|
||||
try {
|
||||
const createWorkplace = async (workplace) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_id = null,
|
||||
@@ -148,17 +102,10 @@ const createWorkplace = async (workplace, callback) => {
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 작업장 조회 (카테고리 정보 포함)
|
||||
*/
|
||||
const getAllWorkplaces = async (callback) => {
|
||||
try {
|
||||
const getAllWorkplaces = async () => {
|
||||
const db = await getDb();
|
||||
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,
|
||||
@@ -168,17 +115,10 @@ const getAllWorkplaces = async (callback) => {
|
||||
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`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 작업장만 조회
|
||||
*/
|
||||
const getActiveWorkplaces = async (callback) => {
|
||||
try {
|
||||
const getActiveWorkplaces = async () => {
|
||||
const db = await getDb();
|
||||
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,
|
||||
@@ -189,17 +129,10 @@ const getActiveWorkplaces = async (callback) => {
|
||||
WHERE w.is_active = TRUE
|
||||
ORDER BY wc.display_order ASC, w.workplace_id DESC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리별 작업장 조회
|
||||
*/
|
||||
const getWorkplacesByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const getWorkplacesByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
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,
|
||||
@@ -211,17 +144,10 @@ const getWorkplacesByCategory = async (categoryId, callback) => {
|
||||
ORDER BY w.workplace_id DESC`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* ID로 작업장 조회
|
||||
*/
|
||||
const getWorkplaceById = async (workplaceId, callback) => {
|
||||
try {
|
||||
const getWorkplaceById = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
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,
|
||||
@@ -232,17 +158,10 @@ const getWorkplaceById = async (workplaceId, callback) => {
|
||||
WHERE w.workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 수정
|
||||
*/
|
||||
const updateWorkplace = async (workplaceId, workplace, callback) => {
|
||||
try {
|
||||
const updateWorkplace = async (workplaceId, workplace) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 삭제
|
||||
*/
|
||||
const deleteWorkplace = async (workplaceId, callback) => {
|
||||
try {
|
||||
const deleteWorkplace = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplaces WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 작업장 지도 영역 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 지도 영역 생성
|
||||
*/
|
||||
const createMapRegion = async (region, callback) => {
|
||||
try {
|
||||
const createMapRegion = async (region) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
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]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리(공장)별 지도 영역 조회
|
||||
*/
|
||||
const getMapRegionsByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const getMapRegionsByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT mr.*, w.workplace_name, w.description
|
||||
@@ -330,33 +228,19 @@ const getMapRegionsByCategory = async (categoryId, callback) => {
|
||||
ORDER BY mr.region_id ASC`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장별 지도 영역 조회
|
||||
*/
|
||||
const getMapRegionByWorkplace = async (workplaceId, callback) => {
|
||||
try {
|
||||
const getMapRegionByWorkplace = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 지도 영역 수정
|
||||
*/
|
||||
const updateMapRegion = async (regionId, region, callback) => {
|
||||
try {
|
||||
const updateMapRegion = async (regionId, region) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
x_start,
|
||||
@@ -374,54 +258,34 @@ const updateMapRegion = async (regionId, region, callback) => {
|
||||
[x_start, y_start, x_end, y_end, shape, polygon_points, regionId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 지도 영역 삭제
|
||||
*/
|
||||
const deleteMapRegion = async (regionId, callback) => {
|
||||
try {
|
||||
const deleteMapRegion = async (regionId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE region_id = ?`,
|
||||
[regionId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 영역 일괄 삭제 (카테고리별)
|
||||
*/
|
||||
const deleteMapRegionsByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const deleteMapRegionsByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// 카테고리
|
||||
createCategory,
|
||||
getAllCategories,
|
||||
getActiveCategories,
|
||||
getCategoryById,
|
||||
updateCategory,
|
||||
deleteCategory,
|
||||
|
||||
// 작업장
|
||||
createWorkplace,
|
||||
getAllWorkplaces,
|
||||
getActiveWorkplaces,
|
||||
@@ -429,8 +293,6 @@ module.exports = {
|
||||
getWorkplaceById,
|
||||
updateWorkplace,
|
||||
deleteWorkplace,
|
||||
|
||||
// 지도 영역
|
||||
createMapRegion,
|
||||
getMapRegionsByCategory,
|
||||
getMapRegionByWorkplace,
|
||||
|
||||
@@ -24,16 +24,10 @@ const createWorkReportService = async (reportData) => {
|
||||
|
||||
logger.info('작업 보고서 생성 요청', { count: reports.length });
|
||||
|
||||
const workReport_ids = [];
|
||||
|
||||
try {
|
||||
const workReport_ids = [];
|
||||
for (const report of reports) {
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workReportModel.create(report, (err, insertId) => {
|
||||
if (err) reject(err);
|
||||
else resolve(insertId);
|
||||
});
|
||||
});
|
||||
const id = await workReportModel.create(report);
|
||||
workReport_ids.push(id);
|
||||
}
|
||||
|
||||
@@ -66,15 +60,8 @@ const getWorkReportsByDateService = async (date) => {
|
||||
logger.info('작업 보고서 날짜별 조회 요청', { date });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getAllByDate(date, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const rows = await workReportModel.getAllByDate(date);
|
||||
logger.info('작업 보고서 조회 성공', { date, count: rows.length });
|
||||
|
||||
return rows;
|
||||
} catch (error) {
|
||||
logger.error('작업 보고서 조회 실패', { date, error: error.message });
|
||||
@@ -96,15 +83,8 @@ const getWorkReportsInRangeService = async (start, end) => {
|
||||
logger.info('작업 보고서 기간별 조회 요청', { start, end });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getByRange(start, end, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const rows = await workReportModel.getByRange(start, end);
|
||||
logger.info('작업 보고서 조회 성공', { start, end, count: rows.length });
|
||||
|
||||
return rows;
|
||||
} catch (error) {
|
||||
logger.error('작업 보고서 조회 실패', { start, end, error: error.message });
|
||||
@@ -123,12 +103,7 @@ const getWorkReportByIdService = async (id) => {
|
||||
logger.info('작업 보고서 조회 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const row = await new Promise((resolve, reject) => {
|
||||
workReportModel.getById(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const row = await workReportModel.getById(id);
|
||||
|
||||
if (!row) {
|
||||
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
|
||||
@@ -136,13 +111,9 @@ const getWorkReportByIdService = async (id) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 조회 성공', { report_id: id });
|
||||
|
||||
return row;
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -159,12 +130,7 @@ const updateWorkReportService = async (id, updateData) => {
|
||||
logger.info('작업 보고서 수정 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
workReportModel.update(id, updateData, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
const changes = await workReportModel.update(id, updateData);
|
||||
|
||||
if (changes === 0) {
|
||||
logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id });
|
||||
@@ -172,13 +138,9 @@ const updateWorkReportService = async (id, updateData) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 수정 성공', { report_id: id, changes });
|
||||
|
||||
return { changes };
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -195,12 +157,7 @@ const removeWorkReportService = async (id) => {
|
||||
logger.info('작업 보고서 삭제 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
workReportModel.remove(id, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
const changes = await workReportModel.remove(id);
|
||||
|
||||
if (changes === 0) {
|
||||
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
|
||||
@@ -208,13 +165,9 @@ const removeWorkReportService = async (id) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 삭제 성공', { report_id: id, changes });
|
||||
|
||||
return { changes };
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -237,35 +190,18 @@ const getSummaryService = async (year, month) => {
|
||||
logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getByRange(start, end, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workReportModel.getByRange(start, end);
|
||||
|
||||
if (!rows || rows.length === 0) {
|
||||
logger.warn('월간 요약 데이터 없음', { year, month });
|
||||
throw new NotFoundError('해당 기간의 작업 보고서가 없습니다');
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 월간 요약 조회 성공', {
|
||||
year,
|
||||
month,
|
||||
count: rows.length
|
||||
});
|
||||
|
||||
logger.info('작업 보고서 월간 요약 조회 성공', { year, month, count: rows.length });
|
||||
return rows;
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.error('작업 보고서 월간 요약 조회 실패', {
|
||||
year,
|
||||
month,
|
||||
error: error.message
|
||||
});
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 월간 요약 조회 실패', { year, month, error: error.message });
|
||||
throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다');
|
||||
}
|
||||
};
|
||||
@@ -274,7 +210,6 @@ const getSummaryService = async (year, month) => {
|
||||
|
||||
/**
|
||||
* 작업 보고서의 부적합 원인 목록 조회
|
||||
* - error_type_id 또는 issue_report_id 중 하나로 연결
|
||||
*/
|
||||
const getReportDefectsService = async (reportId) => {
|
||||
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 db = await getDb();
|
||||
try {
|
||||
await db.query('START TRANSACTION');
|
||||
|
||||
// 기존 부적합 원인 삭제
|
||||
await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]);
|
||||
|
||||
// 새 부적합 원인 추가
|
||||
if (defects && defects.length > 0) {
|
||||
for (const defect of defects) {
|
||||
// issue_report_id > category_id/item_id > error_type_id 순으로 우선
|
||||
await db.execute(`
|
||||
INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -344,12 +273,10 @@ const saveReportDefectsService = async (reportId, defects) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 총 부적합 시간 계산 및 daily_work_reports 업데이트
|
||||
const totalErrorHours = defects
|
||||
? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0)
|
||||
: 0;
|
||||
|
||||
// 첫 번째 defect의 error_type_id를 대표값으로 (레거시 호환)
|
||||
const firstErrorTypeId = defects && defects.length > 0
|
||||
? (defects.find(d => d.error_type_id)?.error_type_id || null)
|
||||
: null;
|
||||
@@ -380,7 +307,6 @@ const saveReportDefectsService = async (reportId, defects) => {
|
||||
|
||||
/**
|
||||
* 부적합 원인 추가 (단일)
|
||||
* - issue_report_id 또는 error_type_id 중 하나 사용
|
||||
*/
|
||||
const addReportDefectService = async (reportId, defectData) => {
|
||||
const db = await getDb();
|
||||
@@ -396,7 +322,6 @@ const addReportDefectService = async (reportId, defectData) => {
|
||||
defectData.note || null
|
||||
]);
|
||||
|
||||
// 총 부적합 시간 업데이트
|
||||
await updateTotalErrorHours(reportId);
|
||||
|
||||
logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId });
|
||||
@@ -413,7 +338,6 @@ const addReportDefectService = async (reportId, defectData) => {
|
||||
const removeReportDefectService = async (defectId) => {
|
||||
const db = await getDb();
|
||||
try {
|
||||
// report_id 먼저 조회
|
||||
const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]);
|
||||
if (defect.length === 0) {
|
||||
throw new NotFoundError('부적합 원인을 찾을 수 없습니다');
|
||||
@@ -421,10 +345,7 @@ const removeReportDefectService = async (defectId) => {
|
||||
|
||||
const reportId = defect[0].report_id;
|
||||
|
||||
// 삭제
|
||||
await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]);
|
||||
|
||||
// 총 부적합 시간 업데이트
|
||||
await updateTotalErrorHours(reportId);
|
||||
|
||||
logger.info('부적합 원인 삭제 성공', { defectId, reportId });
|
||||
@@ -438,7 +359,6 @@ const removeReportDefectService = async (defectId) => {
|
||||
|
||||
/**
|
||||
* 총 부적합 시간 업데이트 헬퍼
|
||||
* - issue_report_id가 있는 경우도 고려
|
||||
*/
|
||||
const updateTotalErrorHours = async (reportId) => {
|
||||
const db = await getDb();
|
||||
@@ -450,8 +370,6 @@ const updateTotalErrorHours = async (reportId) => {
|
||||
|
||||
const totalErrorHours = result[0].total || 0;
|
||||
|
||||
// 첫 번째 부적합 원인의 error_type_id를 대표값으로 사용 (레거시 호환)
|
||||
// issue_report_id만 있는 경우 error_type_id는 null
|
||||
const [firstDefect] = await db.execute(`
|
||||
SELECT error_type_id FROM work_report_defects
|
||||
WHERE report_id = ? AND error_type_id IS NOT NULL
|
||||
@@ -480,7 +398,6 @@ module.exports = {
|
||||
updateWorkReportService,
|
||||
removeWorkReportService,
|
||||
getSummaryService,
|
||||
// 부적합 원인 관리
|
||||
getReportDefectsService,
|
||||
saveReportDefectsService,
|
||||
addReportDefectService,
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('createWorkReportService', () => {
|
||||
it('단일 보고서를 성공적으로 생성해야 함', async () => {
|
||||
// Arrange
|
||||
const reportData = {
|
||||
report_date: '2025-12-11',
|
||||
worker_id: 1,
|
||||
@@ -29,46 +28,32 @@ describe('WorkReportService', () => {
|
||||
work_content: '기능 개발'
|
||||
};
|
||||
|
||||
// workReportModel.create가 콜백 형태이므로 모킹 설정
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callback(null, 123); // insertId = 123
|
||||
});
|
||||
workReportModel.create.mockResolvedValue(123);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.createWorkReportService(reportData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ workReport_ids: [123] });
|
||||
expect(workReportModel.create).toHaveBeenCalledTimes(1);
|
||||
expect(workReportModel.create).toHaveBeenCalledWith(
|
||||
reportData,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.create).toHaveBeenCalledWith(reportData);
|
||||
});
|
||||
|
||||
it('다중 보고서를 성공적으로 생성해야 함', async () => {
|
||||
// Arrange
|
||||
const reportsData = [
|
||||
{ report_date: '2025-12-11', worker_id: 1, work_hours: 8 },
|
||||
{ report_date: '2025-12-11', worker_id: 2, work_hours: 7 }
|
||||
];
|
||||
|
||||
let callCount = 0;
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callCount++;
|
||||
callback(null, 100 + callCount);
|
||||
});
|
||||
workReportModel.create
|
||||
.mockResolvedValueOnce(101)
|
||||
.mockResolvedValueOnce(102);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.createWorkReportService(reportsData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ workReport_ids: [101, 102] });
|
||||
expect(workReportModel.create).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('빈 배열이면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.createWorkReportService([]))
|
||||
.rejects.toThrow(ValidationError);
|
||||
|
||||
@@ -77,14 +62,10 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('DB 오류 시 DatabaseError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
const reportData = { report_date: '2025-12-11', worker_id: 1 };
|
||||
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callback(new Error('DB connection failed'), null);
|
||||
});
|
||||
workReportModel.create.mockRejectedValue(new Error('DB connection failed'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.createWorkReportService(reportData))
|
||||
.rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -92,24 +73,18 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportsByDateService', () => {
|
||||
it('날짜로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const date = '2025-12-11';
|
||||
const mockReports = mockWorkReports.filter(r => r.report_date === date);
|
||||
|
||||
workReportModel.getAllByDate = jest.fn((date, callback) => {
|
||||
callback(null, mockReports);
|
||||
});
|
||||
workReportModel.getAllByDate.mockResolvedValue(mockReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportsByDateService(date);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockReports);
|
||||
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date, expect.any(Function));
|
||||
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date);
|
||||
});
|
||||
|
||||
it('날짜가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsByDateService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
|
||||
@@ -118,12 +93,8 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('DB 오류 시 DatabaseError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getAllByDate = jest.fn((date, callback) => {
|
||||
callback(new Error('DB error'), null);
|
||||
});
|
||||
workReportModel.getAllByDate.mockRejectedValue(new Error('DB error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsByDateService('2025-12-11'))
|
||||
.rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -131,28 +102,19 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportByIdService', () => {
|
||||
it('ID로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const mockReport = mockWorkReports[0];
|
||||
|
||||
workReportModel.getById = jest.fn((id, callback) => {
|
||||
callback(null, mockReport);
|
||||
});
|
||||
workReportModel.getById.mockResolvedValue(mockReport);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportByIdService(1);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockReport);
|
||||
expect(workReportModel.getById).toHaveBeenCalledWith(1, expect.any(Function));
|
||||
expect(workReportModel.getById).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getById = jest.fn((id, callback) => {
|
||||
callback(null, null);
|
||||
});
|
||||
workReportModel.getById.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportByIdService(999))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
|
||||
@@ -161,7 +123,6 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportByIdService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -169,38 +130,24 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('updateWorkReportService', () => {
|
||||
it('보고서를 성공적으로 수정해야 함', async () => {
|
||||
// Arrange
|
||||
const updateData = { work_hours: 9 };
|
||||
|
||||
workReportModel.update = jest.fn((id, data, callback) => {
|
||||
callback(null, 1); // affectedRows = 1
|
||||
});
|
||||
workReportModel.update.mockResolvedValue(1);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.updateWorkReportService(123, updateData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ changes: 1 });
|
||||
expect(workReportModel.update).toHaveBeenCalledWith(
|
||||
123,
|
||||
updateData,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.update).toHaveBeenCalledWith(123, updateData);
|
||||
});
|
||||
|
||||
it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.update = jest.fn((id, data, callback) => {
|
||||
callback(null, 0); // affectedRows = 0
|
||||
});
|
||||
workReportModel.update.mockResolvedValue(0);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.updateWorkReportService(999, {}))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.updateWorkReportService(null, {}))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -208,32 +155,22 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('removeWorkReportService', () => {
|
||||
it('보고서를 성공적으로 삭제해야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.remove = jest.fn((id, callback) => {
|
||||
callback(null, 1);
|
||||
});
|
||||
workReportModel.remove.mockResolvedValue(1);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.removeWorkReportService(123);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ changes: 1 });
|
||||
expect(workReportModel.remove).toHaveBeenCalledWith(123, expect.any(Function));
|
||||
expect(workReportModel.remove).toHaveBeenCalledWith(123);
|
||||
});
|
||||
|
||||
it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.remove = jest.fn((id, callback) => {
|
||||
callback(null, 0);
|
||||
});
|
||||
workReportModel.remove.mockResolvedValue(0);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.removeWorkReportService(999))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.removeWorkReportService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -241,34 +178,23 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportsInRangeService', () => {
|
||||
it('기간으로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const start = '2025-12-01';
|
||||
const end = '2025-12-31';
|
||||
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, mockWorkReports);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue(mockWorkReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportsInRangeService(start, end);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockWorkReports);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalledWith(
|
||||
start,
|
||||
end,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalledWith(start, end);
|
||||
});
|
||||
|
||||
it('시작일이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31'))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('종료일이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -276,41 +202,30 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getSummaryService', () => {
|
||||
it('월간 요약을 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const year = '2025';
|
||||
const month = '12';
|
||||
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, mockWorkReports);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue(mockWorkReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getSummaryService(year, month);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockWorkReports);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('연도가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService(null, '12'))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('월이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService('2025', null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('데이터가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, []);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue([]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService('2025', '12'))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user