From 5183e9ff85abb3c968ee745bcf56836471ec1d90 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 25 Feb 2026 09:40:33 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20System=201=20=EB=AA=A8=EB=8D=B8/?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=BD=9C=EB=B0=B1?= =?UTF-8?q?=E2=86=92async/await=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 11개 모델 파일의 171개 콜백 메서드를 직접 return/throw 패턴으로 변환. 8개 컨트롤러에서 new Promise 래퍼와 중첩 콜백 제거, console.error→logger.error 교체. 미사용 pageAccessModel.js 삭제. 전체 -3,600줄 감소. Co-Authored-By: Claude Opus 4.6 --- .../controllers/dailyWorkReportController.js | 679 +++---- .../api/controllers/equipmentController.js | 820 +++------ .../api/controllers/tbmController.js | 1286 +++++--------- .../controllers/vacationBalanceController.js | 326 +--- .../api/controllers/vacationTypeController.js | 305 +--- .../api/controllers/visitRequestController.js | 689 +++----- .../api/controllers/workIssueController.js | 795 ++++----- .../api/controllers/workplaceController.js | 255 +-- .../api/models/dailyIssueReportModel.js | 138 +- .../api/models/dailyWorkReportModel.js | 1052 +++++------ system1-factory/api/models/equipmentModel.js | 1365 +++++++------- system1-factory/api/models/pageAccessModel.js | 160 -- system1-factory/api/models/tbmModel.js | 1571 +++++++---------- .../api/models/tbmTransferModel.js | 209 +-- .../api/models/vacationBalanceModel.js | 354 ++-- .../api/models/vacationTypeModel.js | 138 +- .../api/models/visitRequestModel.js | 690 +++----- system1-factory/api/models/workIssueModel.js | 1289 ++++++-------- system1-factory/api/models/workReportModel.js | 252 ++- system1-factory/api/models/workplaceModel.js | 584 +++--- .../api/services/workReportService.js | 111 +- .../unit/services/workReportService.test.js | 129 +- 22 files changed, 4791 insertions(+), 8406 deletions(-) delete mode 100644 system1-factory/api/models/pageAccessModel.js diff --git a/system1-factory/api/controllers/dailyWorkReportController.js b/system1-factory/api/controllers/dailyWorkReportController.js index 9c79629..e01621a 100644 --- a/system1-factory/api/controllers/dailyWorkReportController.js +++ b/system1-factory/api/controllers/dailyWorkReportController.js @@ -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,75 +33,53 @@ 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}`); + const data = await dailyWorkReportModel.getContributorsByDate(date, worker_id); - try { - const data = await new Promise((resolve, reject) => { - dailyWorkReportModel.getContributorsByDate(date, worker_id, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - - const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0); - - console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`); - - const result = { - date, - worker_id, - contributors: data, - total_contributors: data.length, - grand_total_hours: totalHours - }; - - res.success(result, '기여자별 요약 조회 성공'); - } catch (err) { - handleDatabaseError(err, '기여자별 요약 조회'); - } + const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0); + + const result = { + date, + worker_id, + contributors: data, + total_contributors: data.length, + grand_total_hours: totalHours + }; + + res.success(result, '기여자별 요약 조회 성공'); }); /** - * 📊 개인 누적 현황 조회 (새로운 기능) + * 개인 누적 현황 조회 */ -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; if (!date || !worker_id) { - return res.status(400).json({ + return res.status(400).json({ error: 'date와 worker_id가 필요합니다.', example: 'date=2024-06-16&worker_id=1' }); } if (!created_by) { - return res.status(401).json({ - error: '사용자 인증 정보가 없습니다.' + return res.status(401).json({ + error: '사용자 인증 정보가 없습니다.' }); } - 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,52 +87,55 @@ 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; if (!deleted_by) { - return res.status(401).json({ - error: '사용자 인증 정보가 없습니다.' + return res.status(401).json({ + error: '사용자 인증 정보가 없습니다.' }); } - console.log(`🗑️ 개별 항목 삭제 요청: id=${id}, 삭제자=${deleted_by}`); + try { + const result = await dailyWorkReportModel.removeSpecificEntry(id, deleted_by); - dailyWorkReportModel.removeSpecificEntry(id, deleted_by, (err, result) => { - if (err) { - console.error('개별 항목 삭제 오류:', err); - return res.status(500).json({ - error: '항목 삭제 중 오류가 발생했습니다.', - details: err.message - }); - } - - console.log(`✅ 개별 항목 삭제 완료: id=${id}`); - res.json({ + res.json({ message: '항목이 성공적으로 삭제되었습니다.', id: id, deleted_by, timestamp: new Date().toISOString(), ...result }); - }); + } catch (err) { + logger.error('개별 항목 삭제 오류:', err); + res.status(500).json({ + error: '항목 삭제 중 오류가 발생했습니다.', + details: err.message + }); + } }; /** - * 📊 작업보고서 조회 (V2 - Service Layer 사용) + * 작업보고서 조회 (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) { @@ -162,73 +143,55 @@ const getDailyWorkReports = async (req, res) => { } const reports = await dailyWorkReportService.getDailyWorkReportsService(req.query, userInfo); - + res.json(reports); } catch (error) { - console.error('💥 작업보고서 조회 컨트롤러 오류:', error.message); - res.status(400).json({ + logger.error('작업보고서 조회 컨트롤러 오류:', error.message); + res.status(400).json({ success: false, error: '작업보고서 조회에 실패했습니다.', - details: error.message + details: error.message }); } }; /** - * 📊 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원) + * 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원) */ -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({ - error: '사용자 인증 정보가 없습니다.' + return res.status(401).json({ + error: '사용자 인증 정보가 없습니다.' }); } - 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({ - 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); - }); + // 임시로 모든 사용자에게 전체 조회 허용 + res.json(data); + } catch (err) { + logger.error('날짜별 작업보고서 조회 오류:', err); + res.status(500).json({ + error: '작업보고서 조회 중 오류가 발생했습니다.', + details: err.message + }); + } }; /** - * 🔍 작업보고서 검색 (페이지네이션 포함) + * 작업보고서 검색 (페이지네이션 포함) */ -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; - + if (!start_date || !end_date) { - return res.status(400).json({ + return res.status(400).json({ error: 'start_date와 end_date가 필요합니다.', example: 'start_date=2024-01-01&end_date=2024-01-31', optional: ['worker_id', 'project_id', 'work_status_id', 'page', 'limit'] @@ -236,8 +199,8 @@ const searchWorkReports = (req, res) => { } if (!created_by) { - return res.status(401).json({ - error: '사용자 인증 정보가 없습니다.' + return res.status(401).json({ + error: '사용자 인증 정보가 없습니다.' }); } @@ -247,53 +210,49 @@ 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({ - error: '작업보고서 검색 중 오류가 발생했습니다.', - details: err.message - }); - } - - console.log(`🔍 검색 결과: ${data.reports?.length || 0}개 (전체: ${data.total || 0}개)`); + try { + const data = await dailyWorkReportModel.searchWithDetails(searchParams); res.json(data); - }); + } catch (err) { + logger.error('작업보고서 검색 오류:', err); + res.status(500).json({ + error: '작업보고서 검색 중 오류가 발생했습니다.', + details: err.message + }); + } }; /** - * 📈 통계 조회 (V2 - Service Layer 사용) + * 통계 조회 (V2 - Service Layer 사용) */ const getWorkReportStats = async (req, res) => { try { const statsData = await dailyWorkReportService.getStatisticsService(req.query); res.json(statsData); } catch (error) { - console.error('💥 통계 조회 컨트롤러 오류:', error.message); - res.status(400).json({ + logger.error('통계 조회 컨트롤러 오류:', error.message); + res.status(400).json({ success: false, error: '통계 조회에 실패했습니다.', - details: error.message + details: error.message }); } }; /** - * 📊 일일 근무 요약 조회 (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,30 +262,22 @@ const getDailySummary = async (req, res) => { }; /** - * 📅 월간 요약 조회 + * 월간 요약 조회 */ -const getMonthlySummary = (req, res) => { +const getMonthlySummary = async (req, res) => { const { year, month } = req.query; if (!year || !month) { - return res.status(400).json({ + return res.status(400).json({ error: 'year와 month가 필요합니다.', example: 'year=2024&month=01', note: 'month는 01, 02, ..., 12 형식으로 입력하세요.' }); } - console.log(`📅 월간 요약 조회: ${year}-${month}`); + try { + const data = await dailyWorkReportModel.getMonthlySummary(year, month); - dailyWorkReportModel.getMonthlySummary(year, month, (err, data) => { - if (err) { - console.error('월간 요약 조회 오류:', err); - return res.status(500).json({ - error: '월간 요약 조회 중 오류가 발생했습니다.', - details: err.message - }); - } - res.json({ year: parseInt(year), month: parseInt(month), @@ -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 { @@ -354,7 +311,7 @@ const updateWorkReport = async (req, res) => { } const result = await dailyWorkReportService.updateWorkReportService(reportId, updateData, userInfo); - + res.json({ success: true, timestamp: new Date().toISOString(), @@ -362,18 +319,18 @@ 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({ + res.status(statusCode).json({ success: false, error: '작업보고서 수정에 실패했습니다.', - details: error.message + details: error.message }); } }; /** - * 🗑️ 특정 작업보고서 삭제 (V2 - Service Layer 사용) + * 특정 작업보고서 삭제 (V2 - Service Layer 사용) * 권한: 그룹장(group_leader), 시스템(system), 관리자(admin)만 가능 */ const removeDailyWorkReport = async (req, res) => { @@ -391,14 +348,14 @@ const removeDailyWorkReport = async (req, res) => { // 권한 체크: 그룹장, 시스템, 관리자만 삭제 가능 const allowedRoles = ['admin', 'system', 'group_leader']; if (!allowedRoles.includes(userInfo.access_level)) { - return res.status(403).json({ + return res.status(403).json({ error: '작업보고서 삭제 권한이 없습니다.', details: '그룹장 이상의 권한이 필요합니다.' }); } - + const result = await dailyWorkReportService.removeDailyWorkReportService(reportId, userInfo); - + res.json({ success: true, timestamp: new Date().toISOString(), @@ -406,60 +363,51 @@ 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({ + res.status(statusCode).json({ success: false, error: '작업보고서 삭제에 실패했습니다.', - details: error.message + details: error.message }); } }; /** - * ��️ 작업자의 특정 날짜 전체 삭제 + * 작업자의 특정 날짜 전체 삭제 */ -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; if (!deleted_by) { - return res.status(401).json({ - error: '사용자 인증 정보가 없습니다.' + return res.status(401).json({ + error: '사용자 인증 정보가 없습니다.' }); } // 권한 체크: 그룹장, 시스템, 관리자만 삭제 가능 const allowedRoles = ['admin', 'system', 'group_leader']; if (!allowedRoles.includes(access_level)) { - return res.status(403).json({ + return res.status(403).json({ error: '작업보고서 삭제 권한이 없습니다.', details: '그룹장 이상의 권한이 필요합니다.' }); } - console.log(`🗑️ 날짜+작업자별 전체 삭제 요청: date=${date}, worker_id=${worker_id}, 삭제자=${deleted_by}`); - - dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by, (err, affectedRows) => { - if (err) { - console.error('작업보고서 전체 삭제 오류:', err); - return res.status(500).json({ - error: '작업보고서 삭제 중 오류가 발생했습니다.', - details: err.message - }); - } + try { + const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by); if (affectedRows === 0) { - return res.status(404).json({ + return res.status(404).json({ error: '삭제할 작업보고서를 찾을 수 없습니다.', date: date, - worker_id: worker_id + worker_id: worker_id }); } - console.log(`✅ 날짜+작업자별 전체 삭제 완료: ${affectedRows}개`); - res.json({ + res.json({ message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`, date, worker_id, @@ -467,356 +415,245 @@ 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({ - success: false, - error: { - message: '작업 유형 조회 중 오류가 발생했습니다.', - code: 'DATABASE_ERROR' - } - }); - } - console.log(`📋 작업 유형 조회 결과: ${data.length}개`); +const getWorkTypes = async (req, res) => { + try { + const data = await dailyWorkReportModel.getAllWorkTypes(); res.json({ success: true, data: data, message: '작업 유형 조회 성공' }); - }); + } catch (err) { + logger.error('작업 유형 조회 오류:', err); + res.status(500).json({ + success: false, + error: { + message: '작업 유형 조회 중 오류가 발생했습니다.', + code: 'DATABASE_ERROR' + } + }); + } }; -const getWorkStatusTypes = (req, res) => { - console.log('📋 업무 상태 유형 조회 요청'); - dailyWorkReportModel.getAllWorkStatusTypes((err, data) => { - if (err) { - console.error('업무 상태 유형 조회 오류:', err); - return res.status(500).json({ - error: '업무 상태 유형 조회 중 오류가 발생했습니다.', - details: err.message - }); - } - console.log(`📋 업무 상태 유형 조회 결과: ${data.length}개`); +const getWorkStatusTypes = async (req, res) => { + try { + const data = await dailyWorkReportModel.getAllWorkStatusTypes(); res.json(data); - }); + } catch (err) { + logger.error('업무 상태 유형 조회 오류:', err); + res.status(500).json({ + error: '업무 상태 유형 조회 중 오류가 발생했습니다.', + details: err.message + }); + } }; -const getErrorTypes = (req, res) => { - console.log('📋 에러 유형 조회 요청'); - dailyWorkReportModel.getAllErrorTypes((err, data) => { - if (err) { - console.error('에러 유형 조회 오류:', err); - return res.status(500).json({ - error: '에러 유형 조회 중 오류가 발생했습니다.', - details: err.message - }); - } - console.log(`📋 에러 유형 조회 결과: ${data.length}개`); +const getErrorTypes = async (req, res) => { + try { + const data = await dailyWorkReportModel.getAllErrorTypes(); res.json(data); - }); + } catch (err) { + logger.error('에러 유형 조회 오류:', err); + res.status(500).json({ + error: '에러 유형 조회 중 오류가 발생했습니다.', + details: err.message + }); + } }; // ========== 작업 유형 CRUD ========== /** - * 📝 작업 유형 생성 + * 작업 유형 생성 */ const createWorkType = asyncHandler(async (req, res) => { const { name, description, category } = req.body; - + if (!name) { - throw new ApiError('작업 유형 이름이 필요합니다.', 400); - } - - console.log('📝 작업 유형 생성:', { name, description, category }); - - try { - const result = await new Promise((resolve, reject) => { - dailyWorkReportModel.createWorkType({ name, description, category }, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - - res.created(result, '작업 유형이 성공적으로 생성되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 유형 생성'); + return res.status(400).json({ error: '작업 유형 이름이 필요합니다.' }); } + + const result = await dailyWorkReportModel.createWorkType({ name, description, category }); + res.created(result, '작업 유형이 성공적으로 생성되었습니다.'); }); /** - * ✏️ 작업 유형 수정 + * 작업 유형 수정 */ const updateWorkType = asyncHandler(async (req, res) => { const { id } = req.params; const { name, description, category } = req.body; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('수정할 작업 유형을 찾을 수 없습니다.', 404); - } - - res.success(result, '작업 유형이 성공적으로 수정되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 유형 수정'); + + const result = await dailyWorkReportModel.updateWorkType(id, { name, description, category }); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '수정할 작업 유형을 찾을 수 없습니다.' }); } + + res.success(result, '작업 유형이 성공적으로 수정되었습니다.'); }); /** - * 🗑️ 작업 유형 삭제 + * 작업 유형 삭제 */ const deleteWorkType = asyncHandler(async (req, res) => { const { id } = req.params; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('삭제할 작업 유형을 찾을 수 없습니다.', 404); - } - - res.success(result, '작업 유형이 성공적으로 삭제되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 유형 삭제'); + + const result = await dailyWorkReportModel.deleteWorkType(id); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '삭제할 작업 유형을 찾을 수 없습니다.' }); } + + res.success(result, '작업 유형이 성공적으로 삭제되었습니다.'); }); // ========== 작업 상태 CRUD ========== /** - * 📝 작업 상태 생성 + * 작업 상태 생성 */ const createWorkStatus = asyncHandler(async (req, res) => { const { name, description, is_error } = req.body; - + if (!name) { - throw new ApiError('작업 상태 이름이 필요합니다.', 400); - } - - console.log('📝 작업 상태 생성:', { name, description, is_error }); - - try { - const result = await new Promise((resolve, reject) => { - dailyWorkReportModel.createWorkStatus({ name, description, is_error }, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - - res.created(result, '작업 상태가 성공적으로 생성되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 상태 생성'); + return res.status(400).json({ error: '작업 상태 이름이 필요합니다.' }); } + + const result = await dailyWorkReportModel.createWorkStatus({ name, description, is_error }); + res.created(result, '작업 상태가 성공적으로 생성되었습니다.'); }); /** - * ✏️ 작업 상태 수정 + * 작업 상태 수정 */ const updateWorkStatus = asyncHandler(async (req, res) => { const { id } = req.params; const { name, description, is_error } = req.body; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('수정할 작업 상태를 찾을 수 없습니다.', 404); - } - - res.success(result, '작업 상태가 성공적으로 수정되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 상태 수정'); + + const result = await dailyWorkReportModel.updateWorkStatus(id, { name, description, is_error }); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '수정할 작업 상태를 찾을 수 없습니다.' }); } + + res.success(result, '작업 상태가 성공적으로 수정되었습니다.'); }); /** - * 🗑️ 작업 상태 삭제 + * 작업 상태 삭제 */ const deleteWorkStatus = asyncHandler(async (req, res) => { const { id } = req.params; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('삭제할 작업 상태를 찾을 수 없습니다.', 404); - } - - res.success(result, '작업 상태가 성공적으로 삭제되었습니다.'); - } catch (err) { - handleDatabaseError(err, '작업 상태 삭제'); + + const result = await dailyWorkReportModel.deleteWorkStatus(id); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '삭제할 작업 상태를 찾을 수 없습니다.' }); } + + res.success(result, '작업 상태가 성공적으로 삭제되었습니다.'); }); // ========== 오류 유형 CRUD ========== /** - * 📝 오류 유형 생성 + * 오류 유형 생성 */ const createErrorType = asyncHandler(async (req, res) => { const { name, description, severity } = req.body; - + if (!name) { - throw new ApiError('오류 유형 이름이 필요합니다.', 400); - } - - console.log('📝 오류 유형 생성:', { name, description, severity }); - - try { - const result = await new Promise((resolve, reject) => { - dailyWorkReportModel.createErrorType({ name, description, severity }, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - - res.created(result, '오류 유형이 성공적으로 생성되었습니다.'); - } catch (err) { - handleDatabaseError(err, '오류 유형 생성'); + return res.status(400).json({ error: '오류 유형 이름이 필요합니다.' }); } + + const result = await dailyWorkReportModel.createErrorType({ name, description, severity }); + res.created(result, '오류 유형이 성공적으로 생성되었습니다.'); }); /** - * ✏️ 오류 유형 수정 + * 오류 유형 수정 */ const updateErrorType = asyncHandler(async (req, res) => { const { id } = req.params; const { name, description, severity } = req.body; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('수정할 오류 유형을 찾을 수 없습니다.', 404); - } - - res.success(result, '오류 유형이 성공적으로 수정되었습니다.'); - } catch (err) { - handleDatabaseError(err, '오류 유형 수정'); + + const result = await dailyWorkReportModel.updateErrorType(id, { name, description, severity }); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '수정할 오류 유형을 찾을 수 없습니다.' }); } + + res.success(result, '오류 유형이 성공적으로 수정되었습니다.'); }); /** - * 🗑️ 오류 유형 삭제 + * 오류 유형 삭제 */ const deleteErrorType = asyncHandler(async (req, res) => { const { id } = req.params; - + if (!id) { - 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); - }); - }); - - if (result.affectedRows === 0) { - throw new ApiError('삭제할 오류 유형을 찾을 수 없습니다.', 404); - } - - res.success(result, '오류 유형이 성공적으로 삭제되었습니다.'); - } catch (err) { - handleDatabaseError(err, '오류 유형 삭제'); + + const result = await dailyWorkReportModel.deleteErrorType(id); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: '삭제할 오류 유형을 찾을 수 없습니다.' }); } + + res.success(result, '오류 유형이 성공적으로 삭제되었습니다.'); }); /** - * 📊 누적 현황 조회 + * 누적 현황 조회 */ -const getAccumulatedReports = (req, res) => { +const getAccumulatedReports = async (req, res) => { const { date, worker_id } = req.query; if (!date || !worker_id) { - return res.status(400).json({ + return res.status(400).json({ error: 'date와 worker_id가 필요합니다.', example: 'date=2024-06-16&worker_id=1' }); } - console.log(`📊 누적 현황 조회: date=${date}, worker_id=${worker_id}`); + 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, @@ -931,4 +772,4 @@ module.exports = { createErrorType, updateErrorType, deleteErrorType -}; \ No newline at end of file +}; diff --git a/system1-factory/api/controllers/equipmentController.js b/system1-factory/api/controllers/equipmentController.js index 896eb75..2ceb301 100644 --- a/system1-factory/api/controllers/equipmentController.js +++ b/system1-factory/api/controllers/equipmentController.js @@ -1,6 +1,7 @@ // controllers/equipmentController.js const EquipmentModel = require('../models/equipmentModel'); const imageUploadService = require('../services/imageUploadService'); +const logger = require('../utils/logger'); const EquipmentController = { // CREATE - 설비 생성 @@ -8,7 +9,6 @@ const EquipmentController = { try { const equipmentData = req.body; - // 필수 필드 검증 if (!equipmentData.equipment_code || !equipmentData.equipment_name) { return res.status(400).json({ success: false, @@ -16,42 +16,22 @@ const EquipmentController = { }); } - // 설비 코드 중복 확인 - EquipmentModel.checkDuplicateCode(equipmentData.equipment_code, null, (error, isDuplicate) => { - if (error) { - console.error('설비 코드 중복 확인 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 코드 중복 확인 중 오류가 발생했습니다.' - }); - } - - if (isDuplicate) { - return res.status(409).json({ - success: false, - message: '이미 사용 중인 설비 코드입니다.' - }); - } - - // 설비 생성 - EquipmentModel.create(equipmentData, (error, result) => { - if (error) { - console.error('설비 생성 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 생성 중 오류가 발생했습니다.' - }); - } - - res.status(201).json({ - success: true, - message: '설비가 성공적으로 생성되었습니다.', - data: result - }); + const isDuplicate = await EquipmentModel.checkDuplicateCode(equipmentData.equipment_code, null); + if (isDuplicate) { + return res.status(409).json({ + success: false, + message: '이미 사용 중인 설비 코드입니다.' }); + } + + const result = await EquipmentModel.create(equipmentData); + res.status(201).json({ + success: true, + message: '설비가 성공적으로 생성되었습니다.', + data: result }); - } catch (error) { - console.error('설비 생성 오류:', error); + } catch (err) { + logger.error('설비 생성 오류:', err); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' @@ -59,8 +39,8 @@ const EquipmentController = { } }, - // READ ALL - 모든 설비 조회 (필터링 가능) - getAllEquipments: (req, res) => { + // READ ALL - 모든 설비 조회 + getAllEquipments: async (req, res) => { try { const filters = { workplace_id: req.query.workplace_id, @@ -69,114 +49,61 @@ const EquipmentController = { search: req.query.search }; - EquipmentModel.getAll(filters, (error, results) => { - if (error) { - console.error('설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('설비 조회 오류:', error); + const results = await EquipmentModel.getAll(filters); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 조회 중 오류가 발생했습니다.' }); } }, // READ ONE - 특정 설비 조회 - getEquipmentById: (req, res) => { + getEquipmentById: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.getById(equipmentId, (error, result) => { - if (error) { - console.error('설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 조회 중 오류가 발생했습니다.' - }); - } - - if (!result) { - return res.status(404).json({ - success: false, - message: '설비를 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - data: result + const result = await EquipmentModel.getById(req.params.id); + if (!result) { + return res.status(404).json({ + success: false, + message: '설비를 찾을 수 없습니다.' }); - }); - } catch (error) { - console.error('설비 조회 오류:', error); + } + res.json({ success: true, data: result }); + } catch (err) { + logger.error('설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 조회 중 오류가 발생했습니다.' }); } }, // READ BY WORKPLACE - 특정 작업장의 설비 조회 - getEquipmentsByWorkplace: (req, res) => { + getEquipmentsByWorkplace: async (req, res) => { try { - const workplaceId = req.params.workplaceId; - - EquipmentModel.getByWorkplace(workplaceId, (error, results) => { - if (error) { - console.error('작업장 설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '작업장 설비 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('작업장 설비 조회 오류:', error); + const results = await EquipmentModel.getByWorkplace(req.params.workplaceId); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('작업장 설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '작업장 설비 조회 중 오류가 발생했습니다.' }); } }, // READ ACTIVE - 활성 설비만 조회 - getActiveEquipments: (req, res) => { + getActiveEquipments: async (req, res) => { try { - EquipmentModel.getActive((error, results) => { - if (error) { - console.error('활성 설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '활성 설비 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('활성 설비 조회 오류:', error); + const results = await EquipmentModel.getActive(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('활성 설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '활성 설비 조회 중 오류가 발생했습니다.' }); } }, @@ -187,7 +114,6 @@ const EquipmentController = { const equipmentId = req.params.id; const equipmentData = req.body; - // 필수 필드 검증 if (!equipmentData.equipment_code || !equipmentData.equipment_name) { return res.status(400).json({ success: false, @@ -195,60 +121,30 @@ const EquipmentController = { }); } - // 설비 존재 확인 - EquipmentModel.getById(equipmentId, (error, existingEquipment) => { - if (error) { - console.error('설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 조회 중 오류가 발생했습니다.' - }); - } - - if (!existingEquipment) { - return res.status(404).json({ - success: false, - message: '설비를 찾을 수 없습니다.' - }); - } - - // 설비 코드 중복 확인 (자신 제외) - EquipmentModel.checkDuplicateCode(equipmentData.equipment_code, equipmentId, (error, isDuplicate) => { - if (error) { - console.error('설비 코드 중복 확인 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 코드 중복 확인 중 오류가 발생했습니다.' - }); - } - - if (isDuplicate) { - return res.status(409).json({ - success: false, - message: '이미 사용 중인 설비 코드입니다.' - }); - } - - // 설비 수정 - EquipmentModel.update(equipmentId, equipmentData, (error, result) => { - if (error) { - console.error('설비 수정 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 수정 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비가 성공적으로 수정되었습니다.', - data: result - }); - }); + const existingEquipment = await EquipmentModel.getById(equipmentId); + if (!existingEquipment) { + return res.status(404).json({ + success: false, + message: '설비를 찾을 수 없습니다.' }); + } + + const isDuplicate = await EquipmentModel.checkDuplicateCode(equipmentData.equipment_code, equipmentId); + if (isDuplicate) { + return res.status(409).json({ + success: false, + message: '이미 사용 중인 설비 코드입니다.' + }); + } + + const result = await EquipmentModel.update(equipmentId, equipmentData); + res.json({ + success: true, + message: '설비가 성공적으로 수정되었습니다.', + data: result }); - } catch (error) { - console.error('설비 수정 오류:', error); + } catch (err) { + logger.error('설비 수정 오류:', err); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' @@ -257,7 +153,7 @@ const EquipmentController = { }, // UPDATE MAP POSITION - 지도상 위치 업데이트 - updateMapPosition: (req, res) => { + updateMapPosition: async (req, res) => { try { const equipmentId = req.params.id; const positionData = { @@ -267,117 +163,71 @@ const EquipmentController = { map_height_percent: req.body.map_height_percent }; - // workplace_id가 있으면 포함 (설비를 다른 작업장으로 이동 가능) if (req.body.workplace_id !== undefined) { positionData.workplace_id = req.body.workplace_id; } - EquipmentModel.updateMapPosition(equipmentId, positionData, (error, result) => { - if (error) { - console.error('설비 위치 업데이트 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 위치 업데이트 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비 위치가 성공적으로 업데이트되었습니다.', - data: result - }); + const result = await EquipmentModel.updateMapPosition(equipmentId, positionData); + res.json({ + success: true, + message: '설비 위치가 성공적으로 업데이트되었습니다.', + data: result }); - } catch (error) { - console.error('설비 위치 업데이트 오류:', error); + } catch (err) { + logger.error('설비 위치 업데이트 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 위치 업데이트 중 오류가 발생했습니다.' }); } }, // DELETE - 설비 삭제 - deleteEquipment: (req, res) => { + deleteEquipment: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.delete(equipmentId, (error, result) => { - if (error) { - console.error('설비 삭제 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 삭제 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비가 성공적으로 삭제되었습니다.', - data: result - }); + const result = await EquipmentModel.delete(req.params.id); + res.json({ + success: true, + message: '설비가 성공적으로 삭제되었습니다.', + data: result }); - } catch (error) { - console.error('설비 삭제 오류:', error); + } catch (err) { + logger.error('설비 삭제 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 삭제 중 오류가 발생했습니다.' }); } }, // GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회 - getEquipmentTypes: (req, res) => { + getEquipmentTypes: async (req, res) => { try { - EquipmentModel.getEquipmentTypes((error, results) => { - if (error) { - console.error('설비 유형 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 유형 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('설비 유형 조회 오류:', error); + const results = await EquipmentModel.getEquipmentTypes(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('설비 유형 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 유형 조회 중 오류가 발생했습니다.' }); } }, // GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 - getNextEquipmentCode: (req, res) => { + getNextEquipmentCode: async (req, res) => { try { const prefix = req.query.prefix || 'TKP'; - - EquipmentModel.getNextEquipmentCode(prefix, (error, nextCode) => { - if (error) { - console.error('다음 관리번호 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '다음 관리번호 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: { - next_code: nextCode, - prefix: prefix - } - }); + const nextCode = await EquipmentModel.getNextEquipmentCode(prefix); + res.json({ + success: true, + data: { next_code: nextCode, prefix } }); - } catch (error) { - console.error('다음 관리번호 조회 오류:', error); + } catch (err) { + logger.error('다음 관리번호 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '다음 관리번호 조회 중 오류가 발생했습니다.' }); } }, @@ -386,7 +236,6 @@ const EquipmentController = { // 설비 사진 관리 // ========================================== - // ADD PHOTO - 설비 사진 추가 addPhoto: async (req, res) => { try { const equipmentId = req.params.id; @@ -399,13 +248,7 @@ const EquipmentController = { }); } - // Base64 이미지를 파일로 저장 - const photoPath = await imageUploadService.saveBase64Image( - photo_base64, - 'equipment', - 'equipments' - ); - + const photoPath = await imageUploadService.saveBase64Image(photo_base64, 'equipment', 'equipments'); if (!photoPath) { return res.status(500).json({ success: false, @@ -413,7 +256,6 @@ const EquipmentController = { }); } - // DB에 사진 정보 저장 const photoData = { photo_path: photoPath, description: description || null, @@ -421,23 +263,14 @@ const EquipmentController = { uploaded_by: req.user?.user_id || null }; - EquipmentModel.addPhoto(equipmentId, photoData, (error, result) => { - if (error) { - console.error('사진 정보 저장 오류:', error); - return res.status(500).json({ - success: false, - message: '사진 정보 저장 중 오류가 발생했습니다.' - }); - } - - res.status(201).json({ - success: true, - message: '사진이 성공적으로 추가되었습니다.', - data: result - }); + const result = await EquipmentModel.addPhoto(equipmentId, photoData); + res.status(201).json({ + success: true, + message: '사진이 성공적으로 추가되었습니다.', + data: result }); - } catch (error) { - console.error('사진 추가 오류:', error); + } catch (err) { + logger.error('사진 추가 오류:', err); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' @@ -445,70 +278,43 @@ const EquipmentController = { } }, - // GET PHOTOS - 설비 사진 조회 - getPhotos: (req, res) => { + getPhotos: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.getPhotos(equipmentId, (error, results) => { - if (error) { - console.error('사진 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '사진 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('사진 조회 오류:', error); + const results = await EquipmentModel.getPhotos(req.params.id); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('사진 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '사진 조회 중 오류가 발생했습니다.' }); } }, - // DELETE PHOTO - 설비 사진 삭제 deletePhoto: async (req, res) => { try { - const photoId = req.params.photoId; + const result = await EquipmentModel.deletePhoto(req.params.photoId); - EquipmentModel.deletePhoto(photoId, async (error, result) => { - if (error) { - if (error.message === 'Photo not found') { - return res.status(404).json({ - success: false, - message: '사진을 찾을 수 없습니다.' - }); - } - console.error('사진 삭제 오류:', error); - return res.status(500).json({ - success: false, - message: '사진 삭제 중 오류가 발생했습니다.' - }); - } + if (result.photo_path) { + await imageUploadService.deleteFile(result.photo_path); + } - // 파일 시스템에서 사진 삭제 - if (result.photo_path) { - await imageUploadService.deleteFile(result.photo_path); - } - - res.json({ - success: true, - message: '사진이 성공적으로 삭제되었습니다.', - data: { photo_id: photoId } - }); + res.json({ + success: true, + message: '사진이 성공적으로 삭제되었습니다.', + data: { photo_id: req.params.photoId } }); - } catch (error) { - console.error('사진 삭제 오류:', error); + } catch (err) { + if (err.message === 'Photo not found') { + return res.status(404).json({ + success: false, + message: '사진을 찾을 수 없습니다.' + }); + } + logger.error('사진 삭제 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '사진 삭제 중 오류가 발생했습니다.' }); } }, @@ -517,8 +323,7 @@ const EquipmentController = { // 설비 임시 이동 // ========================================== - // MOVE TEMPORARILY - 설비 임시 이동 - moveTemporarily: (req, res) => { + moveTemporarily: async (req, res) => { try { const equipmentId = req.params.id; const moveData = { @@ -541,116 +346,66 @@ const EquipmentController = { }); } - EquipmentModel.moveTemporarily(equipmentId, moveData, (error, result) => { - if (error) { - console.error('설비 이동 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 이동 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비가 임시 이동되었습니다.', - data: result - }); + const result = await EquipmentModel.moveTemporarily(equipmentId, moveData); + res.json({ + success: true, + message: '설비가 임시 이동되었습니다.', + data: result }); - } catch (error) { - console.error('설비 이동 오류:', error); + } catch (err) { + logger.error('설비 이동 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 이동 중 오류가 발생했습니다.' }); } }, - // RETURN TO ORIGINAL - 설비 원위치 복귀 - returnToOriginal: (req, res) => { + returnToOriginal: async (req, res) => { try { - const equipmentId = req.params.id; - const userId = req.user?.user_id || null; - - EquipmentModel.returnToOriginal(equipmentId, userId, (error, result) => { - if (error) { - if (error.message === 'Equipment not found') { - return res.status(404).json({ - success: false, - message: '설비를 찾을 수 없습니다.' - }); - } - console.error('설비 복귀 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 복귀 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비가 원위치로 복귀되었습니다.', - data: result - }); + const result = await EquipmentModel.returnToOriginal(req.params.id, req.user?.user_id || null); + res.json({ + success: true, + message: '설비가 원위치로 복귀되었습니다.', + data: result }); - } catch (error) { - console.error('설비 복귀 오류:', error); + } catch (err) { + if (err.message === 'Equipment not found') { + return res.status(404).json({ + success: false, + message: '설비를 찾을 수 없습니다.' + }); + } + logger.error('설비 복귀 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 복귀 중 오류가 발생했습니다.' }); } }, - // GET TEMPORARILY MOVED - 임시 이동된 설비 목록 - getTemporarilyMoved: (req, res) => { + getTemporarilyMoved: async (req, res) => { try { - EquipmentModel.getTemporarilyMoved((error, results) => { - if (error) { - console.error('임시 이동 설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '임시 이동 설비 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('임시 이동 설비 조회 오류:', error); + const results = await EquipmentModel.getTemporarilyMoved(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('임시 이동 설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '임시 이동 설비 조회 중 오류가 발생했습니다.' }); } }, - // GET MOVE LOGS - 설비 이동 이력 조회 - getMoveLogs: (req, res) => { + getMoveLogs: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.getMoveLogs(equipmentId, (error, results) => { - if (error) { - console.error('이동 이력 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '이동 이력 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('이동 이력 조회 오류:', error); + const results = await EquipmentModel.getMoveLogs(req.params.id); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('이동 이력 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '이동 이력 조회 중 오류가 발생했습니다.' }); } }, @@ -659,12 +414,10 @@ const EquipmentController = { // 설비 외부 반출/반입 // ========================================== - // EXPORT EQUIPMENT - 설비 외부 반출 - exportEquipment: (req, res) => { + exportEquipment: async (req, res) => { try { - const equipmentId = req.params.id; const exportData = { - equipment_id: equipmentId, + equipment_id: req.params.id, export_date: req.body.export_date, expected_return_date: req.body.expected_return_date, destination: req.body.destination, @@ -674,34 +427,23 @@ const EquipmentController = { exported_by: req.user?.user_id || null }; - EquipmentModel.exportEquipment(exportData, (error, result) => { - if (error) { - console.error('설비 반출 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 반출 중 오류가 발생했습니다.' - }); - } - - res.status(201).json({ - success: true, - message: '설비가 외부로 반출되었습니다.', - data: result - }); + const result = await EquipmentModel.exportEquipment(exportData); + res.status(201).json({ + success: true, + message: '설비가 외부로 반출되었습니다.', + data: result }); - } catch (error) { - console.error('설비 반출 오류:', error); + } catch (err) { + logger.error('설비 반출 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 반출 중 오류가 발생했습니다.' }); } }, - // RETURN EQUIPMENT - 설비 반입 (외부에서 복귀) - returnEquipment: (req, res) => { + returnEquipment: async (req, res) => { try { - const logId = req.params.logId; const returnData = { return_date: req.body.return_date, new_status: req.body.new_status || 'active', @@ -709,86 +451,49 @@ const EquipmentController = { returned_by: req.user?.user_id || null }; - EquipmentModel.returnEquipment(logId, returnData, (error, result) => { - if (error) { - if (error.message === 'Export log not found') { - return res.status(404).json({ - success: false, - message: '반출 기록을 찾을 수 없습니다.' - }); - } - console.error('설비 반입 오류:', error); - return res.status(500).json({ - success: false, - message: '설비 반입 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - message: '설비가 반입되었습니다.', - data: result - }); + const result = await EquipmentModel.returnEquipment(req.params.logId, returnData); + res.json({ + success: true, + message: '설비가 반입되었습니다.', + data: result }); - } catch (error) { - console.error('설비 반입 오류:', error); + } catch (err) { + if (err.message === 'Export log not found') { + return res.status(404).json({ + success: false, + message: '반출 기록을 찾을 수 없습니다.' + }); + } + logger.error('설비 반입 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '설비 반입 중 오류가 발생했습니다.' }); } }, - // GET EXTERNAL LOGS - 설비 외부 반출 이력 조회 - getExternalLogs: (req, res) => { + getExternalLogs: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.getExternalLogs(equipmentId, (error, results) => { - if (error) { - console.error('반출 이력 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '반출 이력 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('반출 이력 조회 오류:', error); + const results = await EquipmentModel.getExternalLogs(req.params.id); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('반출 이력 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '반출 이력 조회 중 오류가 발생했습니다.' }); } }, - // GET EXPORTED EQUIPMENTS - 현재 외부 반출 중인 설비 목록 - getExportedEquipments: (req, res) => { + getExportedEquipments: async (req, res) => { try { - EquipmentModel.getExportedEquipments((error, results) => { - if (error) { - console.error('반출 중 설비 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '반출 중 설비 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('반출 중 설비 조회 오류:', error); + const results = await EquipmentModel.getExportedEquipments(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('반출 중 설비 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '반출 중 설비 조회 중 오류가 발생했습니다.' }); } }, @@ -797,13 +502,11 @@ const EquipmentController = { // 설비 수리 신청 // ========================================== - // CREATE REPAIR REQUEST - 수리 신청 createRepairRequest: async (req, res) => { try { const equipmentId = req.params.id; const { photo_base64_list, description, item_id, workplace_id } = req.body; - // 사진 저장 (있는 경우) let photoPaths = []; if (photo_base64_list && photo_base64_list.length > 0) { for (const base64 of photo_base64_list) { @@ -821,92 +524,54 @@ const EquipmentController = { reported_by: req.user?.user_id || null }; - EquipmentModel.createRepairRequest(requestData, (error, result) => { - if (error) { - if (error.message === '설비 수리 카테고리가 없습니다') { - return res.status(400).json({ - success: false, - message: error.message - }); - } - console.error('수리 신청 오류:', error); - return res.status(500).json({ - success: false, - message: '수리 신청 중 오류가 발생했습니다.' - }); - } - - res.status(201).json({ - success: true, - message: '수리 신청이 접수되었습니다.', - data: result - }); + const result = await EquipmentModel.createRepairRequest(requestData); + res.status(201).json({ + success: true, + message: '수리 신청이 접수되었습니다.', + data: result }); - } catch (error) { - console.error('수리 신청 오류:', error); + } catch (err) { + if (err.message === '설비 수리 카테고리가 없습니다') { + return res.status(400).json({ + success: false, + message: err.message + }); + } + logger.error('수리 신청 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '수리 신청 중 오류가 발생했습니다.' }); } }, - // GET REPAIR HISTORY - 설비 수리 이력 조회 - getRepairHistory: (req, res) => { + getRepairHistory: async (req, res) => { try { - const equipmentId = req.params.id; - - EquipmentModel.getRepairHistory(equipmentId, (error, results) => { - if (error) { - console.error('수리 이력 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '수리 이력 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('수리 이력 조회 오류:', error); + const results = await EquipmentModel.getRepairHistory(req.params.id); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('수리 이력 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '수리 이력 조회 중 오류가 발생했습니다.' }); } }, - // GET REPAIR CATEGORIES - 설비 수리 항목 목록 조회 - getRepairCategories: (req, res) => { + getRepairCategories: async (req, res) => { try { - EquipmentModel.getRepairCategories((error, results) => { - if (error) { - console.error('수리 항목 조회 오류:', error); - return res.status(500).json({ - success: false, - message: '수리 항목 조회 중 오류가 발생했습니다.' - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('수리 항목 조회 오류:', error); + const results = await EquipmentModel.getRepairCategories(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('수리 항목 조회 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '수리 항목 조회 중 오류가 발생했습니다.' }); } }, - // ADD REPAIR CATEGORY - 새 수리 항목 추가 - addRepairCategory: (req, res) => { + addRepairCategory: async (req, res) => { try { const { item_name } = req.body; @@ -917,26 +582,17 @@ const EquipmentController = { }); } - EquipmentModel.addRepairCategory(item_name.trim(), (error, result) => { - if (error) { - console.error('수리 항목 추가 오류:', error); - return res.status(500).json({ - success: false, - message: '수리 항목 추가 중 오류가 발생했습니다.' - }); - } - - res.status(201).json({ - success: true, - message: result.isNew ? '새 수리 유형이 추가되었습니다.' : '기존 수리 유형을 사용합니다.', - data: result - }); + const result = await EquipmentModel.addRepairCategory(item_name.trim()); + res.status(201).json({ + success: true, + message: result.isNew ? '새 수리 유형이 추가되었습니다.' : '기존 수리 유형을 사용합니다.', + data: result }); - } catch (error) { - console.error('수리 항목 추가 오류:', error); + } catch (err) { + logger.error('수리 항목 추가 오류:', err); res.status(500).json({ success: false, - message: '서버 오류가 발생했습니다.' + message: '수리 항목 추가 중 오류가 발생했습니다.' }); } } diff --git a/system1-factory/api/controllers/tbmController.js b/system1-factory/api/controllers/tbmController.js index 8baba23..52447ea 100644 --- a/system1-factory/api/controllers/tbmController.js +++ b/system1-factory/api/controllers/tbmController.js @@ -1,515 +1,299 @@ // controllers/tbmController.js - TBM 시스템 컨트롤러 const TbmModel = require('../models/tbmModel'); const TbmTransferModel = require('../models/tbmTransferModel'); +const logger = require('../utils/logger'); const TbmController = { // ==================== TBM 세션 관련 ==================== - /** - * TBM 세션 생성 - */ - createSession: (req, res) => { - const sessionData = { - session_date: req.body.session_date, - leader_id: req.body.leader_id || null, - project_id: req.body.project_id || null, - work_location: req.body.work_location || null, - work_description: req.body.work_description || null, - safety_notes: req.body.safety_notes || null, - start_time: req.body.start_time || null, - created_by: req.user.user_id - }; + createSession: async (req, res) => { + try { + const sessionData = { + session_date: req.body.session_date, + leader_id: req.body.leader_id || null, + project_id: req.body.project_id || null, + work_location: req.body.work_location || null, + work_description: req.body.work_description || null, + safety_notes: req.body.safety_notes || null, + start_time: req.body.start_time || null, + created_by: req.user.user_id + }; - // 필수 필드 검증 (날짜만 필수, leader_id는 관리자의 경우 null 허용) - if (!sessionData.session_date) { - return res.status(400).json({ - success: false, - message: 'TBM 날짜는 필수입니다.' - }); - } - - TbmModel.createSession(sessionData, (err, result) => { - if (err) { - console.error('TBM 세션 생성 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 생성 중 오류가 발생했습니다.', - error: err.message - }); + if (!sessionData.session_date) { + return res.status(400).json({ success: false, message: 'TBM 날짜는 필수입니다.' }); } + const result = await TbmModel.createSession(sessionData); res.status(201).json({ success: true, message: 'TBM 세션이 생성되었습니다.', - data: { - session_id: result.insertId, - ...sessionData - } - }); - }); - }, - - /** - * 특정 날짜의 TBM 세션 목록 조회 - */ - getSessionsByDate: (req, res) => { - const { date } = req.params; - - if (!date) { - return res.status(400).json({ - success: false, - message: '날짜 정보가 필요합니다.' + data: { session_id: result.insertId, ...sessionData } }); + } catch (err) { + logger.error('TBM 세션 생성 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 생성 중 오류가 발생했습니다.', error: err.message }); } - - TbmModel.getSessionsByDate(date, (err, results) => { - if (err) { - console.error('TBM 세션 조회 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); }, - /** - * TBM 세션 상세 조회 - */ - getSessionById: (req, res) => { - const { sessionId } = req.params; - - TbmModel.getSessionById(sessionId, (err, results) => { - if (err) { - console.error('TBM 세션 상세 조회 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 상세 조회 중 오류가 발생했습니다.', - error: err.message - }); + getSessionsByDate: async (req, res) => { + try { + const { date } = req.params; + if (!date) { + return res.status(400).json({ success: false, message: '날짜 정보가 필요합니다.' }); } + const results = await TbmModel.getSessionsByDate(date); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('TBM 세션 조회 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 조회 중 오류가 발생했습니다.', error: err.message }); + } + }, + + getSessionById: async (req, res) => { + try { + const { sessionId } = req.params; + const results = await TbmModel.getSessionById(sessionId); + if (results.length === 0) { - return res.status(404).json({ - success: false, - message: 'TBM 세션을 찾을 수 없습니다.' - }); + return res.status(404).json({ success: false, message: 'TBM 세션을 찾을 수 없습니다.' }); } - res.json({ - success: true, - data: results[0] - }); - }); - }, - - /** - * TBM 세션 수정 - */ - updateSession: (req, res) => { - const { sessionId } = req.params; - const sessionData = { - project_id: req.body.project_id, - work_location: req.body.work_location, - work_description: req.body.work_description, - safety_notes: req.body.safety_notes, - status: req.body.status || 'draft' - }; - - TbmModel.updateSession(sessionId, sessionData, (err, result) => { - if (err) { - console.error('TBM 세션 수정 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 수정 중 오류가 발생했습니다.', - error: err.message - }); - } - - if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: 'TBM 세션을 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - message: 'TBM 세션이 수정되었습니다.' - }); - }); - }, - - /** - * TBM 세션 완료 처리 - */ - completeSession: (req, res) => { - const { sessionId } = req.params; - const endTime = req.body.end_time || new Date().toTimeString().slice(0, 8); - const attendanceData = req.body.attendance_data; - - // 근태 데이터가 있으면 새 메서드 사용 - if (attendanceData && Array.isArray(attendanceData) && attendanceData.length > 0) { - const createdBy = req.user.user_id; - TbmModel.completeSessionWithAttendance(sessionId, endTime, attendanceData, createdBy, (err, result) => { - if (err) { - console.error('TBM 세션 완료 처리 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 완료 처리 중 오류가 발생했습니다.', - error: err.message - }); - } - - if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: 'TBM 세션을 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - message: 'TBM 세션이 완료되었습니다.' - }); - }); - return; + res.json({ success: true, data: results[0] }); + } catch (err) { + logger.error('TBM 세션 상세 조회 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 상세 조회 중 오류가 발생했습니다.', error: err.message }); } - - // 기존 방식 (하위 호환) - TbmModel.completeSession(sessionId, endTime, (err, result) => { - if (err) { - console.error('TBM 세션 완료 처리 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 완료 처리 중 오류가 발생했습니다.', - error: err.message - }); - } - - if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: 'TBM 세션을 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - message: 'TBM 세션이 완료되었습니다.' - }); - }); }, - /** - * TBM 세션 삭제 (draft 상태만) - */ - deleteSession: (req, res) => { - const { sessionId } = req.params; + updateSession: async (req, res) => { + try { + const { sessionId } = req.params; + const sessionData = { + project_id: req.body.project_id, + work_location: req.body.work_location, + work_description: req.body.work_description, + safety_notes: req.body.safety_notes, + status: req.body.status || 'draft' + }; - TbmModel.deleteSession(sessionId, (err, result) => { - if (err) { - console.error('TBM 세션 삭제 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 세션 삭제 중 오류가 발생했습니다.', - error: err.message - }); + const result = await TbmModel.updateSession(sessionId, sessionData); + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: 'TBM 세션을 찾을 수 없습니다.' }); + } + + res.json({ success: true, message: 'TBM 세션이 수정되었습니다.' }); + } catch (err) { + logger.error('TBM 세션 수정 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 수정 중 오류가 발생했습니다.', error: err.message }); + } + }, + + completeSession: async (req, res) => { + try { + const { sessionId } = req.params; + const endTime = req.body.end_time || new Date().toTimeString().slice(0, 8); + const attendanceData = req.body.attendance_data; + + let result; + if (attendanceData && Array.isArray(attendanceData) && attendanceData.length > 0) { + result = await TbmModel.completeSessionWithAttendance(sessionId, endTime, attendanceData, req.user.user_id); + } else { + result = await TbmModel.completeSession(sessionId, endTime); } if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: 'TBM 세션을 찾을 수 없거나 이미 완료된 세션입니다.' - }); + return res.status(404).json({ success: false, message: 'TBM 세션을 찾을 수 없습니다.' }); } - res.json({ - success: true, - message: 'TBM 세션이 삭제되었습니다.' - }); - }); + res.json({ success: true, message: 'TBM 세션이 완료되었습니다.' }); + } catch (err) { + logger.error('TBM 세션 완료 처리 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 완료 처리 중 오류가 발생했습니다.', error: err.message }); + } + }, + + deleteSession: async (req, res) => { + try { + const { sessionId } = req.params; + const result = await TbmModel.deleteSession(sessionId); + + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: 'TBM 세션을 찾을 수 없거나 이미 완료된 세션입니다.' }); + } + + res.json({ success: true, message: 'TBM 세션이 삭제되었습니다.' }); + } catch (err) { + logger.error('TBM 세션 삭제 오류:', err); + res.status(500).json({ success: false, message: 'TBM 세션 삭제 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 팀 구성 관련 ==================== - /** - * 팀원 추가 (작업자별 상세 정보 포함) - */ - addTeamMember: (req, res) => { - const assignmentData = { - session_id: req.params.sessionId, - worker_id: req.body.worker_id, - assigned_role: req.body.assigned_role || null, - work_detail: req.body.work_detail || null, - is_present: req.body.is_present, - absence_reason: req.body.absence_reason || null, - project_id: req.body.project_id || null, - work_type_id: req.body.work_type_id || null, - task_id: req.body.task_id || null, - workplace_category_id: req.body.workplace_category_id || null, - workplace_id: req.body.workplace_id || null, - work_hours: req.body.work_hours !== undefined ? req.body.work_hours : undefined - }; + addTeamMember: async (req, res) => { + try { + const assignmentData = { + session_id: req.params.sessionId, + worker_id: req.body.worker_id, + assigned_role: req.body.assigned_role || null, + work_detail: req.body.work_detail || null, + is_present: req.body.is_present, + absence_reason: req.body.absence_reason || null, + project_id: req.body.project_id || null, + work_type_id: req.body.work_type_id || null, + task_id: req.body.task_id || null, + workplace_category_id: req.body.workplace_category_id || null, + workplace_id: req.body.workplace_id || null, + work_hours: req.body.work_hours !== undefined ? req.body.work_hours : undefined + }; - if (!assignmentData.worker_id) { - return res.status(400).json({ - success: false, - message: '작업자 ID가 필요합니다.' - }); - } - - TbmModel.addTeamMember(assignmentData, (err, result) => { - if (err) { - console.error('팀원 추가 오류:', err); - return res.status(500).json({ - success: false, - message: '팀원 추가 중 오류가 발생했습니다.', - error: err.message - }); + if (!assignmentData.worker_id) { + return res.status(400).json({ success: false, message: '작업자 ID가 필요합니다.' }); } - res.json({ - success: true, - message: '팀원이 추가되었습니다.' - }); - }); + await TbmModel.addTeamMember(assignmentData); + res.json({ success: true, message: '팀원이 추가되었습니다.' }); + } catch (err) { + logger.error('팀원 추가 오류:', err); + res.status(500).json({ success: false, message: '팀원 추가 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 분할 항목 추가 (같은 작업자의 추가 배정) - */ - addSplitAssignment: (req, res) => { - const assignmentData = { - session_id: req.params.sessionId, - worker_id: req.body.worker_id, - work_hours: req.body.work_hours, - project_id: req.body.project_id || null, - work_type_id: req.body.work_type_id || null, - task_id: req.body.task_id || null, - workplace_category_id: req.body.workplace_category_id || null, - workplace_id: req.body.workplace_id || null - }; + addSplitAssignment: async (req, res) => { + try { + const assignmentData = { + session_id: req.params.sessionId, + worker_id: req.body.worker_id, + work_hours: req.body.work_hours, + project_id: req.body.project_id || null, + work_type_id: req.body.work_type_id || null, + task_id: req.body.task_id || null, + workplace_category_id: req.body.workplace_category_id || null, + workplace_id: req.body.workplace_id || null + }; - if (!assignmentData.worker_id || !assignmentData.work_hours) { - return res.status(400).json({ success: false, message: '작업자 ID와 작업시간이 필요합니다.' }); - } - - TbmModel.addSplitAssignment(assignmentData, (err, result) => { - if (err) { - console.error('분할 항목 추가 오류:', err); - return res.status(500).json({ success: false, message: '분할 항목 추가 중 오류가 발생했습니다.' }); + if (!assignmentData.worker_id || !assignmentData.work_hours) { + return res.status(400).json({ success: false, message: '작업자 ID와 작업시간이 필요합니다.' }); } + + const result = await TbmModel.addSplitAssignment(assignmentData); res.json({ success: true, data: result }); - }); + } catch (err) { + logger.error('분할 항목 추가 오류:', err); + res.status(500).json({ success: false, message: '분할 항목 추가 중 오류가 발생했습니다.' }); + } }, - /** - * 팀 구성 일괄 추가 - */ - addTeamMembers: (req, res) => { - const { sessionId } = req.params; - const { members } = req.body; + addTeamMembers: async (req, res) => { + try { + const { sessionId } = req.params; + const { members } = req.body; - if (!Array.isArray(members) || members.length === 0) { - return res.status(400).json({ - success: false, - message: '팀원 목록이 필요합니다.' - }); - } - - TbmModel.addTeamMembers(sessionId, members, (err, result) => { - if (err) { - console.error('팀 구성 일괄 추가 오류:', err); - return res.status(500).json({ - success: false, - message: '팀 구성 추가 중 오류가 발생했습니다.', - error: err.message - }); + if (!Array.isArray(members) || members.length === 0) { + return res.status(400).json({ success: false, message: '팀원 목록이 필요합니다.' }); } + await TbmModel.addTeamMembers(sessionId, members); res.json({ success: true, message: `${members.length}명의 팀원이 추가되었습니다.`, data: { count: members.length } }); - }); + } catch (err) { + logger.error('팀 구성 일괄 추가 오류:', err); + res.status(500).json({ success: false, message: '팀 구성 추가 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * TBM 세션의 팀 구성 조회 - */ - getTeamMembers: (req, res) => { - const { sessionId } = req.params; - - TbmModel.getTeamMembers(sessionId, (err, results) => { - if (err) { - console.error('팀 구성 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '팀 구성 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); + getTeamMembers: async (req, res) => { + try { + const results = await TbmModel.getTeamMembers(req.params.sessionId); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('팀 구성 조회 오류:', err); + res.status(500).json({ success: false, message: '팀 구성 조회 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 팀원 제거 - */ - removeTeamMember: (req, res) => { - const { sessionId, workerId } = req.params; - - TbmModel.removeTeamMember(sessionId, workerId, (err, result) => { - if (err) { - console.error('팀원 제거 오류:', err); - return res.status(500).json({ - success: false, - message: '팀원 제거 중 오류가 발생했습니다.', - error: err.message - }); - } + removeTeamMember: async (req, res) => { + try { + const { sessionId, workerId } = req.params; + const result = await TbmModel.removeTeamMember(sessionId, workerId); 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 (err) { + logger.error('팀원 제거 오류:', err); + res.status(500).json({ success: false, message: '팀원 제거 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 세션의 모든 팀원 삭제 (수정 시 사용) - */ - clearAllTeamMembers: (req, res) => { - const { sessionId } = req.params; - - TbmModel.clearAllTeamMembers(sessionId, (err, result) => { - if (err) { - console.error('팀원 전체 삭제 오류:', err); - return res.status(500).json({ - success: false, - message: '팀원 전체 삭제 중 오류가 발생했습니다.', - error: err.message - }); - } - + clearAllTeamMembers: async (req, res) => { + try { + const result = await TbmModel.clearAllTeamMembers(req.params.sessionId); res.json({ success: true, message: '모든 팀원이 삭제되었습니다.', data: { deletedCount: result.affectedRows } }); - }); + } catch (err) { + logger.error('팀원 전체 삭제 오류:', err); + res.status(500).json({ success: false, message: '팀원 전체 삭제 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 안전 체크리스트 관련 ==================== - /** - * 모든 안전 체크 항목 조회 - */ - getAllSafetyChecks: (req, res) => { - TbmModel.getAllSafetyChecks((err, results) => { - if (err) { - console.error('안전 체크 항목 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 항목 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); - }, - - /** - * TBM 세션의 안전 체크 기록 조회 - */ - getSafetyRecords: (req, res) => { - const { sessionId } = req.params; - - TbmModel.getSafetyRecords(sessionId, (err, results) => { - if (err) { - console.error('안전 체크 기록 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 기록 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); - }, - - /** - * 안전 체크 일괄 저장 - */ - saveSafetyRecords: (req, res) => { - const { sessionId } = req.params; - const { records } = req.body; - - if (!Array.isArray(records) || records.length === 0) { - return res.status(400).json({ - success: false, - message: '안전 체크 기록이 필요합니다.' - }); + getAllSafetyChecks: async (req, res) => { + try { + const results = await TbmModel.getAllSafetyChecks(); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('안전 체크 항목 조회 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 항목 조회 중 오류가 발생했습니다.', error: err.message }); } + }, - const checkedBy = req.user.user_id; + getSafetyRecords: async (req, res) => { + try { + const results = await TbmModel.getSafetyRecords(req.params.sessionId); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('안전 체크 기록 조회 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 기록 조회 중 오류가 발생했습니다.', error: err.message }); + } + }, - TbmModel.saveSafetyRecords(sessionId, records, checkedBy, (err, result) => { - if (err) { - console.error('안전 체크 저장 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 저장 중 오류가 발생했습니다.', - error: err.message - }); + saveSafetyRecords: async (req, res) => { + try { + const { sessionId } = req.params; + const { records } = req.body; + + if (!Array.isArray(records) || records.length === 0) { + return res.status(400).json({ success: false, message: '안전 체크 기록이 필요합니다.' }); } + await TbmModel.saveSafetyRecords(sessionId, records, req.user.user_id); res.json({ success: true, message: '안전 체크가 저장되었습니다.', data: { count: records.length } }); - }); + } catch (err) { + logger.error('안전 체크 저장 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 저장 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 필터링된 안전 체크리스트 (확장) ==================== - /** - * 세션에 맞는 필터링된 안전 체크 항목 조회 - * 기본 + 날씨 + 작업별 체크항목 통합 - */ getFilteredSafetyChecks: async (req, res) => { - const { sessionId } = req.params; - try { - // 날씨 정보 확인 (이미 저장된 경우 사용, 없으면 새로 조회) + const { sessionId } = req.params; const weatherService = require('../services/weatherService'); let weatherRecord = await weatherService.getWeatherRecord(sessionId); let weatherConditions = []; @@ -517,41 +301,19 @@ const TbmController = { if (weatherRecord && weatherRecord.weather_conditions) { weatherConditions = weatherRecord.weather_conditions; } else { - // 날씨 정보가 없으면 현재 날씨 조회 const currentWeather = await weatherService.getCurrentWeather(); weatherConditions = await weatherService.determineWeatherConditions(currentWeather); - // 날씨 기록 저장 await weatherService.saveWeatherRecord(sessionId, currentWeather, weatherConditions); } - TbmModel.getFilteredSafetyChecks(sessionId, weatherConditions, (err, results) => { - if (err) { - console.error('필터링된 안전 체크 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크리스트 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); - } catch (error) { - console.error('필터링된 안전 체크 조회 오류:', error); - res.status(500).json({ - success: false, - message: '안전 체크리스트 조회 중 오류가 발생했습니다.', - error: error.message - }); + const results = await TbmModel.getFilteredSafetyChecks(sessionId, weatherConditions); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('필터링된 안전 체크 조회 오류:', err); + res.status(500).json({ success: false, message: '안전 체크리스트 조회 중 오류가 발생했습니다.', error: err.message }); } }, - /** - * 현재 날씨 조회 - */ getCurrentWeather: async (req, res) => { try { const weatherService = require('../services/weatherService'); @@ -561,538 +323,290 @@ const TbmController = { const conditions = await weatherService.determineWeatherConditions(weatherData); const conditionList = await weatherService.getWeatherConditionList(); - // 현재 조건의 상세 정보 매핑 const activeConditions = conditionList.filter(c => conditions.includes(c.condition_code)); res.json({ success: true, - data: { - ...weatherData, - conditions, - conditionDetails: activeConditions - } - }); - } catch (error) { - console.error('날씨 조회 오류:', error); - res.status(500).json({ - success: false, - message: '날씨 조회 중 오류가 발생했습니다.', - error: error.message + data: { ...weatherData, conditions, conditionDetails: activeConditions } }); + } catch (err) { + logger.error('날씨 조회 오류:', err); + res.status(500).json({ success: false, message: '날씨 조회 중 오류가 발생했습니다.', error: err.message }); } }, - /** - * 세션 날씨 정보 저장 - */ saveSessionWeather: async (req, res) => { - const { sessionId } = req.params; - const { weatherConditions } = req.body; - try { + const { sessionId } = req.params; + const { weatherConditions } = req.body; const weatherService = require('../services/weatherService'); - // 현재 날씨 조회 const weatherData = await weatherService.getCurrentWeather(); const conditions = weatherConditions || await weatherService.determineWeatherConditions(weatherData); - - // 저장 await weatherService.saveWeatherRecord(sessionId, weatherData, conditions); - res.json({ - success: true, - message: '날씨 정보가 저장되었습니다.', - data: { conditions } - }); - } catch (error) { - console.error('날씨 저장 오류:', error); - res.status(500).json({ - success: false, - message: '날씨 저장 중 오류가 발생했습니다.', - error: error.message - }); + res.json({ success: true, message: '날씨 정보가 저장되었습니다.', data: { conditions } }); + } catch (err) { + logger.error('날씨 저장 오류:', err); + res.status(500).json({ success: false, message: '날씨 저장 중 오류가 발생했습니다.', error: err.message }); } }, - /** - * 세션 날씨 정보 조회 - */ getSessionWeather: async (req, res) => { - const { sessionId } = req.params; - try { const weatherService = require('../services/weatherService'); - const weatherRecord = await weatherService.getWeatherRecord(sessionId); + const weatherRecord = await weatherService.getWeatherRecord(req.params.sessionId); if (!weatherRecord) { - return res.status(404).json({ - success: false, - message: '날씨 기록이 없습니다.' - }); + return res.status(404).json({ success: false, message: '날씨 기록이 없습니다.' }); } - res.json({ - success: true, - data: weatherRecord - }); - } catch (error) { - console.error('날씨 조회 오류:', error); - res.status(500).json({ - success: false, - message: '날씨 조회 중 오류가 발생했습니다.', - error: error.message - }); + res.json({ success: true, data: weatherRecord }); + } catch (err) { + logger.error('날씨 조회 오류:', err); + res.status(500).json({ success: false, message: '날씨 조회 중 오류가 발생했습니다.', error: err.message }); } }, - /** - * 날씨 조건 목록 조회 - */ getWeatherConditions: async (req, res) => { try { const weatherService = require('../services/weatherService'); const conditions = await weatherService.getWeatherConditionList(); - - res.json({ - success: true, - data: conditions - }); - } catch (error) { - console.error('날씨 조건 조회 오류:', error); - res.status(500).json({ - success: false, - message: '날씨 조건 조회 중 오류가 발생했습니다.', - error: error.message - }); + res.json({ success: true, data: conditions }); + } catch (err) { + logger.error('날씨 조건 조회 오류:', err); + res.status(500).json({ success: false, message: '날씨 조건 조회 중 오류가 발생했습니다.', error: err.message }); } }, // ==================== 안전 체크항목 관리 (관리자용) ==================== - /** - * 안전 체크 항목 생성 - */ - createSafetyCheck: (req, res) => { - const checkData = req.body; - - if (!checkData.check_category || !checkData.check_item) { - return res.status(400).json({ - success: false, - message: '카테고리와 체크 항목은 필수입니다.' - }); - } - - TbmModel.createSafetyCheck(checkData, (err, result) => { - if (err) { - console.error('안전 체크 항목 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 항목 생성 중 오류가 발생했습니다.', - error: err.message - }); + createSafetyCheck: async (req, res) => { + try { + const checkData = req.body; + if (!checkData.check_category || !checkData.check_item) { + return res.status(400).json({ success: false, message: '카테고리와 체크 항목은 필수입니다.' }); } + const result = await TbmModel.createSafetyCheck(checkData); res.status(201).json({ success: true, message: '안전 체크 항목이 생성되었습니다.', data: { check_id: result.insertId } }); - }); + } catch (err) { + logger.error('안전 체크 항목 생성 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 항목 생성 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 안전 체크 항목 수정 - */ - updateSafetyCheck: (req, res) => { - const { checkId } = req.params; - const checkData = req.body; - - TbmModel.updateSafetyCheck(checkId, checkData, (err, result) => { - if (err) { - console.error('안전 체크 항목 수정 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 항목 수정 중 오류가 발생했습니다.', - error: err.message - }); - } - + updateSafetyCheck: async (req, res) => { + try { + const result = await TbmModel.updateSafetyCheck(req.params.checkId, 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: '안전 체크 항목이 수정되었습니다.' - }); - }); + res.json({ success: true, message: '안전 체크 항목이 수정되었습니다.' }); + } catch (err) { + logger.error('안전 체크 항목 수정 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 항목 수정 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 안전 체크 항목 삭제 (비활성화) - */ - deleteSafetyCheck: (req, res) => { - const { checkId } = req.params; - - TbmModel.deleteSafetyCheck(checkId, (err, result) => { - if (err) { - console.error('안전 체크 항목 삭제 오류:', err); - return res.status(500).json({ - success: false, - message: '안전 체크 항목 삭제 중 오류가 발생했습니다.', - error: err.message - }); - } - + deleteSafetyCheck: async (req, res) => { + try { + const result = await TbmModel.deleteSafetyCheck(req.params.checkId); 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 (err) { + logger.error('안전 체크 항목 삭제 오류:', err); + res.status(500).json({ success: false, message: '안전 체크 항목 삭제 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 작업 인계 관련 ==================== - /** - * 작업 인계 생성 - */ - createHandover: (req, res) => { - const handoverData = { - session_id: req.body.session_id, - from_leader_id: req.body.from_leader_id, - to_leader_id: req.body.to_leader_id, - handover_date: req.body.handover_date, - handover_time: req.body.handover_time || null, - reason: req.body.reason, - handover_notes: req.body.handover_notes || null, - worker_ids: req.body.worker_ids || [] - }; + createHandover: async (req, res) => { + try { + const handoverData = { + session_id: req.body.session_id, + from_leader_id: req.body.from_leader_id, + to_leader_id: req.body.to_leader_id, + handover_date: req.body.handover_date, + handover_time: req.body.handover_time || null, + reason: req.body.reason, + handover_notes: req.body.handover_notes || null, + worker_ids: req.body.worker_ids || [] + }; - // 필수 필드 검증 - if (!handoverData.session_id || !handoverData.from_leader_id || - !handoverData.to_leader_id || !handoverData.handover_date || !handoverData.reason) { - return res.status(400).json({ - success: false, - message: '필수 정보가 누락되었습니다.' - }); - } - - TbmModel.createHandover(handoverData, (err, result) => { - if (err) { - console.error('작업 인계 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '작업 인계 생성 중 오류가 발생했습니다.', - error: err.message - }); + if (!handoverData.session_id || !handoverData.from_leader_id || + !handoverData.to_leader_id || !handoverData.handover_date || !handoverData.reason) { + return res.status(400).json({ success: false, message: '필수 정보가 누락되었습니다.' }); } + const result = await TbmModel.createHandover(handoverData); res.status(201).json({ success: true, message: '작업 인계가 생성되었습니다.', data: { handover_id: result.insertId } }); - }); - }, - - /** - * 작업 인계 확인 - */ - confirmHandover: (req, res) => { - const { handoverId } = req.params; - const confirmedBy = req.user.user_id; - - TbmModel.confirmHandover(handoverId, confirmedBy, (err, result) => { - if (err) { - console.error('작업 인계 확인 오류:', err); - return res.status(500).json({ - success: false, - message: '작업 인계 확인 중 오류가 발생했습니다.', - error: err.message - }); - } - - if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: '작업 인계 건을 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - message: '작업 인계가 확인되었습니다.' - }); - }); - }, - - /** - * 특정 날짜의 작업 인계 목록 조회 - */ - getHandoversByDate: (req, res) => { - const { date } = req.params; - - TbmModel.getHandoversByDate(date, (err, results) => { - if (err) { - console.error('작업 인계 목록 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '작업 인계 목록 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); - }, - - /** - * 나에게 온 미확인 인계 건 조회 - */ - getMyPendingHandovers: (req, res) => { - // worker_id는 req.user에서 가져옴 - const toLeaderId = req.user.worker_id; - - if (!toLeaderId) { - return res.status(400).json({ - success: false, - message: '작업자 정보를 찾을 수 없습니다.' - }); + } catch (err) { + logger.error('작업 인계 생성 오류:', err); + res.status(500).json({ success: false, message: '작업 인계 생성 중 오류가 발생했습니다.', error: err.message }); } + }, - TbmModel.getPendingHandovers(toLeaderId, (err, results) => { - if (err) { - console.error('미확인 인계 건 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '미확인 인계 건 조회 중 오류가 발생했습니다.', - error: err.message - }); + confirmHandover: async (req, res) => { + try { + const result = await TbmModel.confirmHandover(req.params.handoverId, req.user.user_id); + if (result.affectedRows === 0) { + 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: '작업 인계 확인 중 오류가 발생했습니다.', error: err.message }); + } + }, + + getHandoversByDate: async (req, res) => { + try { + const results = await TbmModel.getHandoversByDate(req.params.date); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('작업 인계 목록 조회 오류:', err); + res.status(500).json({ success: false, message: '작업 인계 목록 조회 중 오류가 발생했습니다.', error: err.message }); + } + }, + + getMyPendingHandovers: async (req, res) => { + try { + const toLeaderId = req.user.worker_id; + if (!toLeaderId) { + return res.status(400).json({ success: false, message: '작업자 정보를 찾을 수 없습니다.' }); } - res.json({ - success: true, - data: results - }); - }); + const results = await TbmModel.getPendingHandovers(toLeaderId); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('미확인 인계 건 조회 오류:', err); + res.status(500).json({ success: false, message: '미확인 인계 건 조회 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 통계 및 리포트 ==================== - /** - * TBM 통계 조회 - */ - getTbmStatistics: (req, res) => { - const { startDate, endDate } = req.query; - - if (!startDate || !endDate) { - return res.status(400).json({ - success: false, - message: '시작일과 종료일이 필요합니다.' - }); - } - - TbmModel.getTbmStatistics(startDate, endDate, (err, results) => { - if (err) { - console.error('TBM 통계 조회 오류:', err); - return res.status(500).json({ - success: false, - message: 'TBM 통계 조회 중 오류가 발생했습니다.', - error: err.message - }); + getTbmStatistics: async (req, res) => { + try { + const { startDate, endDate } = req.query; + if (!startDate || !endDate) { + return res.status(400).json({ success: false, message: '시작일과 종료일이 필요합니다.' }); } - res.json({ - success: true, - data: results - }); - }); + const results = await TbmModel.getTbmStatistics(startDate, endDate); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('TBM 통계 조회 오류:', err); + res.status(500).json({ success: false, message: 'TBM 통계 조회 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 리더별 TBM 진행 현황 조회 - */ - getLeaderStatistics: (req, res) => { - const { startDate, endDate } = req.query; - - if (!startDate || !endDate) { - return res.status(400).json({ - success: false, - message: '시작일과 종료일이 필요합니다.' - }); - } - - TbmModel.getLeaderStatistics(startDate, endDate, (err, results) => { - if (err) { - console.error('리더 통계 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '리더 통계 조회 중 오류가 발생했습니다.', - error: err.message - }); + getLeaderStatistics: async (req, res) => { + try { + const { startDate, endDate } = req.query; + if (!startDate || !endDate) { + return res.status(400).json({ success: false, message: '시작일과 종료일이 필요합니다.' }); } - res.json({ - success: true, - data: results - }); - }); + const results = await TbmModel.getLeaderStatistics(startDate, endDate); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('리더 통계 조회 오류:', err); + res.status(500).json({ success: false, message: '리더 통계 조회 중 오류가 발생했습니다.', error: err.message }); + } }, // ==================== 작업자 이동 관련 ==================== - /** - * 이동 실행 (보내기/빼오기) - */ - createTransfer: (req, res) => { - const { transfer_type, worker_id, source_session_id, dest_session_id, hours, - project_id, work_type_id, task_id, workplace_category_id, workplace_id } = req.body; + createTransfer: async (req, res) => { + try { + const { transfer_type, worker_id, source_session_id, dest_session_id, hours, + project_id, work_type_id, task_id, workplace_category_id, workplace_id } = req.body; - if (!transfer_type || !worker_id || !source_session_id || !dest_session_id || !hours) { - return res.status(400).json({ - success: false, - message: '필수 정보가 누락되었습니다.' - }); + if (!transfer_type || !worker_id || !source_session_id || !dest_session_id || !hours) { + return res.status(400).json({ success: false, message: '필수 정보가 누락되었습니다.' }); + } + + const today = new Date(); + const transferDate = today.getFullYear() + '-' + + String(today.getMonth() + 1).padStart(2, '0') + '-' + + String(today.getDate()).padStart(2, '0'); + + const transferData = { + transfer_type, worker_id, source_session_id, dest_session_id, + hours, initiated_by: req.user.user_id, transfer_date: transferDate, + project_id, work_type_id, task_id, workplace_category_id, workplace_id + }; + + const result = await TbmTransferModel.createTransfer(transferData); + if (!result.success) { + return res.status(400).json(result); + } + + res.json({ success: true, message: '이동이 완료되었습니다.', data: result }); + } catch (err) { + logger.error('이동 실행 오류:', err); + res.status(500).json({ success: false, message: '이동 실행 중 오류가 발생했습니다.', error: err.message }); } + }, - const today = new Date(); - const transferDate = today.getFullYear() + '-' + - String(today.getMonth() + 1).padStart(2, '0') + '-' + - String(today.getDate()).padStart(2, '0'); - - const transferData = { - transfer_type, worker_id, source_session_id, dest_session_id, - hours, initiated_by: req.user.user_id, transfer_date: transferDate, - project_id, work_type_id, task_id, workplace_category_id, workplace_id - }; - - TbmTransferModel.createTransfer(transferData, (err, result) => { - if (err) { - console.error('이동 실행 오류:', err); - return res.status(500).json({ - success: false, - message: '이동 실행 중 오류가 발생했습니다.', - error: err.message - }); - } + getTransfersByDate: async (req, res) => { + try { + const results = await TbmTransferModel.getTransfersByDate(req.params.date); + res.json({ success: true, data: results }); + } catch (err) { + logger.error('이동 내역 조회 오류:', err); + res.status(500).json({ success: false, message: '이동 내역 조회 중 오류가 발생했습니다.', error: err.message }); + } + }, + cancelTransfer: async (req, res) => { + try { + const result = await TbmTransferModel.cancelTransfer(req.params.transferId); if (!result.success) { return res.status(400).json(result); } - - res.json({ - success: true, - message: '이동이 완료되었습니다.', - data: result - }); - }); + res.json({ success: true, message: '이동이 취소되었습니다.' }); + } catch (err) { + logger.error('이동 취소 오류:', err); + res.status(500).json({ success: false, message: '이동 취소 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 당일 이동 내역 조회 - */ - getTransfersByDate: (req, res) => { - const { date } = req.params; - - TbmTransferModel.getTransfersByDate(date, (err, results) => { - if (err) { - console.error('이동 내역 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '이동 내역 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - + getWorkerAssignmentsByDate: async (req, res) => { + try { + const results = await TbmTransferModel.getWorkerAssignmentsByDate(req.params.date); res.json({ success: true, data: results }); - }); + } catch (err) { + logger.error('배정 현황 조회 오류:', err); + res.status(500).json({ success: false, message: '배정 현황 조회 중 오류가 발생했습니다.', error: err.message }); + } }, - /** - * 이동 취소 (원복) - */ - cancelTransfer: (req, res) => { - const { transferId } = req.params; - - TbmTransferModel.cancelTransfer(transferId, (err, result) => { - if (err) { - console.error('이동 취소 오류:', err); - return res.status(500).json({ - success: false, - message: '이동 취소 중 오류가 발생했습니다.', - error: err.message - }); - } - - if (!result.success) { - return res.status(400).json(result); - } - - res.json({ - success: true, - message: '이동이 취소되었습니다.' - }); - }); - }, - - /** - * 당일 전 작업자 배정 현황 - */ - getWorkerAssignmentsByDate: (req, res) => { - const { date } = req.params; - - TbmTransferModel.getWorkerAssignmentsByDate(date, (err, results) => { - if (err) { - console.error('배정 현황 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '배정 현황 조회 중 오류가 발생했습니다.', - error: err.message - }); - } + getIncompleteWorkReports: async (req, res) => { + try { + const userId = req.user.user_id; + const accessLevel = req.user.access_level; + const filterUserId = (accessLevel === 'system' || accessLevel === 'admin') ? null : userId; + const results = await TbmModel.getIncompleteWorkReports(filterUserId); res.json({ success: true, data: results }); - }); - }, - - /** - * 작업보고서가 작성되지 않은 TBM 팀 배정 조회 - */ - getIncompleteWorkReports: (req, res) => { - const userId = req.user.user_id; - const accessLevel = req.user.access_level; - - // 관리자는 모든 TBM 조회, 일반 사용자는 본인이 작성한 것만 조회 - const filterUserId = (accessLevel === 'system' || accessLevel === 'admin') ? null : userId; - - TbmModel.getIncompleteWorkReports(filterUserId, (err, results) => { - if (err) { - console.error('미완료 작업보고서 조회 오류:', err); - return res.status(500).json({ - success: false, - message: '미완료 작업보고서 조회 중 오류가 발생했습니다.', - error: err.message - }); - } - - res.json({ - success: true, - data: results - }); - }); + } catch (err) { + logger.error('미완료 작업보고서 조회 오류:', err); + res.status(500).json({ success: false, message: '미완료 작업보고서 조회 중 오류가 발생했습니다.', error: err.message }); + } } }; diff --git a/system1-factory/api/controllers/vacationBalanceController.js b/system1-factory/api/controllers/vacationBalanceController.js index 4eeaa3a..2231dc7 100644 --- a/system1-factory/api/controllers/vacationBalanceController.js +++ b/system1-factory/api/controllers/vacationBalanceController.js @@ -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,54 +55,33 @@ 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: '중복 체크 중 오류가 발생했습니다' - }); - } - - if (existing && existing.length > 0) { - return res.status(400).json({ - success: false, - message: '이미 해당 작업자의 해당 연도 휴가 잔액이 존재합니다' - }); - } - - const balanceData = { - worker_id, - vacation_type_id, - year, - total_days, - used_days: used_days || 0, - notes: notes || null, - created_by - }; - - vacationBalanceModel.create(balanceData, (err, result) => { - if (err) { - console.error('휴가 잔액 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '휴가 잔액을 생성하는 중 오류가 발생했습니다' - }); - } - - res.status(201).json({ - success: true, - message: '휴가 잔액이 생성되었습니다', - data: { id: result.insertId } - }); + const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year); + if (existing && existing.length > 0) { + return res.status(400).json({ + success: false, + message: '이미 해당 작업자의 해당 연도 휴가 잔액이 존재합니다' }); + } + + const balanceData = { + worker_id, + vacation_type_id, + year, + total_days, + used_days: used_days || 0, + notes: notes || null, + created_by + }; + + 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: '수정할 데이터가 없습니다' - }); + 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: '휴가 잔액을 수정하는 중 오류가 발생했습니다' - }); - } + const result = await vacationBalanceModel.update(id, updateData); + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' }); + } - if (result.affectedRows === 0) { - 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; + const result = await vacationBalanceModel.delete(id); - vacationBalanceModel.delete(id, (err, result) => { - if (err) { - console.error('휴가 잔액 삭제 오류:', err); - return res.status(500).json({ - success: false, - message: '휴가 잔액을 삭제하는 중 오류가 발생했습니다' - }); - } + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' }); + } - if (result.affectedRows === 0) { - 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,74 +152,44 @@ 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; + 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: '중복 체크 중 오류가 발생했습니다' - }); - } - - if (existing && existing.length > 0) { - return res.status(400).json({ - success: false, - message: '이미 해당 작업자의 해당 연도 연차가 존재합니다' - }); - } - - const balanceData = { - worker_id, - vacation_type_id: annualTypeId, - year, - total_days: annualDays, - used_days: 0, - notes: `근속년수 기반 자동 계산 (입사일: ${hire_date})`, - created_by - }; - - vacationBalanceModel.create(balanceData, (err, result) => { - if (err) { - console.error('휴가 잔액 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '휴가 잔액을 생성하는 중 오류가 발생했습니다' - }); - } - - res.status(201).json({ - success: true, - message: `${annualDays}일의 연차가 자동으로 생성되었습니다`, - data: { - id: result.insertId, - calculated_days: annualDays - } - }); - }); + // 중복 체크 + const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year); + if (existing && existing.length > 0) { + return res.status(400).json({ + success: false, + message: '이미 해당 작업자의 해당 연도 연차가 존재합니다' }); + } + + const balanceData = { + worker_id, + vacation_type_id: annualTypeId, + year, + total_days: annualDays, + used_days: 0, + notes: `근속년수 기반 자동 계산 (입사일: ${hire_date})`, + created_by + }; + + const result = await vacationBalanceModel.create(balanceData); + res.status(201).json({ + success: true, + message: `${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: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' }); } } }; diff --git a/system1-factory/api/controllers/vacationTypeController.js b/system1-factory/api/controllers/vacationTypeController.js index 9eddaeb..da56ee6 100644 --- a/system1-factory/api/controllers/vacationTypeController.js +++ b/system1-factory/api/controllers/vacationTypeController.js @@ -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,56 +65,31 @@ 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입니다' }); + } - if (existingTypes && existingTypes.length > 0) { - return res.status(400).json({ - success: false, - message: '이미 존재하는 type_code입니다' - }); - } + const typeData = { + type_code, + type_name, + deduct_days, + priority: priority || 50, + description: description || null, + is_special: true, + is_system: false, + is_active: true + }; - // 특별 휴가 유형으로 생성 - const typeData = { - type_code, - type_name, - deduct_days, - priority: priority || 50, - description: description || null, - is_special: true, - is_system: false, - is_active: true - }; - - vacationTypeModel.create(typeData, (err, result) => { - if (err) { - console.error('휴가 유형 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '휴가 유형을 생성하는 중 오류가 발생했습니다' - }); - } - - res.status(201).json({ - success: true, - message: '특별 휴가 유형이 생성되었습니다', - data: { id: result.insertId } - }); - }); + 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,78 +100,37 @@ const vacationTypeController = { async updateType(req, res) { try { const { id } = req.params; - const { - type_name, - deduct_days, - priority, - description, - is_active - } = req.body; + 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 types = await vacationTypeModel.getById(id); + if (!types || types.length === 0) { + return res.status(404).json({ success: false, message: '휴가 유형을 찾을 수 없습니다' }); + } - if (!types || types.length === 0) { - return res.status(404).json({ - success: false, - message: '휴가 유형을 찾을 수 없습니다' - }); - } + const type = types[0]; + const updateData = {}; - const type = types[0]; + if (type.is_system) { + 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; + if (description !== undefined) updateData.description = description; + if (is_active !== undefined) updateData.is_active = is_active; + } - // 시스템 기본 휴가의 경우 제한적으로만 수정 가능 - 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; - if (description !== undefined) updateData.description = description; - if (is_active !== undefined) updateData.is_active = is_active; - } + if (Object.keys(updateData).length === 0) { + return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' }); + } - if (Object.keys(updateData).length === 0) { - 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: '휴가 유형이 수정되었습니다' - }); - }); - }); + updateData.updated_at = new Date(); + 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,34 +141,19 @@ const vacationTypeController = { async deleteType(req, res) { try { const { id } = req.params; + const result = await vacationTypeModel.delete(id); - vacationTypeModel.delete(id, (err, result) => { - if (err) { - console.error('휴가 유형 삭제 오류:', err); - return res.status(500).json({ - success: false, - message: '휴가 유형을 삭제하는 중 오류가 발생했습니다' - }); - } - - if (result.affectedRows === 0) { - return res.status(400).json({ - success: false, - message: '삭제할 수 없습니다. 시스템 기본 휴가이거나 존재하지 않는 휴가 유형입니다' - }); - } - - res.json({ - success: true, - message: '휴가 유형이 삭제되었습니다' + if (result.affectedRows === 0) { + return res.status(400).json({ + success: false, + 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 } - }); + res.json({ + success: true, + message: '우선순위가 업데이트되었습니다', + 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: '서버 오류가 발생했습니다' }); } } }; diff --git a/system1-factory/api/controllers/visitRequestController.js b/system1-factory/api/controllers/visitRequestController.js index 8a7e5b6..948be3b 100644 --- a/system1-factory/api/controllers/visitRequestController.js +++ b/system1-factory/api/controllers/visitRequestController.js @@ -1,555 +1,284 @@ const visitRequestModel = require('../models/visitRequestModel'); +const logger = require('../utils/logger'); // ==================== 출입 신청 관리 ==================== -/** - * 출입 신청 생성 - */ -exports.createVisitRequest = (req, res) => { - const requester_id = req.user.user_id; - const requestData = { - requester_id, - ...req.body - }; +exports.createVisitRequest = async (req, res) => { + try { + const requester_id = req.user.user_id; + 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}는 필수 입력 항목입니다.` - }); - } - } - - visitRequestModel.createVisitRequest(requestData, (err, requestId) => { - if (err) { - console.error('출입 신청 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '출입 신청 생성 중 오류가 발생했습니다.', - error: err.message - }); + 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}는 필수 입력 항목입니다.` }); + } } + 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) => { - const filters = { - status: req.query.status, - visit_date: req.query.visit_date, - start_date: req.query.start_date, - end_date: req.query.end_date, - requester_id: req.query.requester_id, - category_id: req.query.category_id - }; +exports.getAllVisitRequests = async (req, res) => { + try { + const filters = { + status: req.query.status, + visit_date: req.query.visit_date, + start_date: req.query.start_date, + end_date: req.query.end_date, + requester_id: req.query.requester_id, + 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 - }); - } - - res.json({ - success: true, - data: requests - }); - }); + const requests = await visitRequestModel.getAllVisitRequests(filters); + res.json({ success: true, data: requests }); + } catch (err) { + logger.error('출입 신청 목록 조회 오류:', err); + res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' }); + } }; -/** - * 출입 신청 상세 조회 - */ -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 - }); - }); + res.json({ success: true, data: request }); + } catch (err) { + logger.error('출입 신청 조회 오류:', err); + res.status(500).json({ success: false, message: '출입 신청 조회 중 오류가 발생했습니다.' }); + } }; -/** - * 출입 신청 수정 - */ -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: '출입 신청이 수정되었습니다.' - }); - }); + res.json({ success: true, message: '출입 신청이 수정되었습니다.' }); + } catch (err) { + logger.error('출입 신청 수정 오류:', err); + res.status(500).json({ success: false, 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: '출입 신청이 삭제되었습니다.' - }); - }); + res.json({ success: true, message: '출입 신청이 삭제되었습니다.' }); + } catch (err) { + logger.error('출입 신청 삭제 오류:', err); + res.status(500).json({ success: false, 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: '출입 신청이 승인되었습니다.' - }); - }); + res.json({ success: true, message: '출입 신청이 승인되었습니다.' }); + } catch (err) { + logger.error('출입 신청 승인 오류:', err); + res.status(500).json({ success: false, message: '출입 신청 승인 중 오류가 발생했습니다.' }); + } }; -/** - * 출입 신청 반려 - */ -exports.rejectVisitRequest = (req, res) => { - const requestId = req.params.id; - const approvedBy = req.user.user_id; - const rejectionReason = req.body.rejection_reason || '사유 없음'; - - const rejectionData = { - approved_by: approvedBy, - rejection_reason: rejectionReason - }; - - visitRequestModel.rejectVisitRequest(requestId, rejectionData, (err, result) => { - if (err) { - console.error('출입 신청 반려 오류:', err); - return res.status(500).json({ - success: false, - message: '출입 신청 반려 중 오류가 발생했습니다.', - error: err.message - }); - } - +exports.rejectVisitRequest = async (req, res) => { + try { + const rejectionData = { + approved_by: req.user.user_id, + rejection_reason: req.body.rejection_reason || '사유 없음' + }; + 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: '출입 신청이 반려되었습니다.' - }); - }); + res.json({ success: true, message: '출입 신청이 반려되었습니다.' }); + } catch (err) { + logger.error('출입 신청 반려 오류:', err); + res.status(500).json({ success: false, 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 - }); - } - - 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 - }); - } - - 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.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: '방문 목적 조회 중 오류가 발생했습니다.' }); } +}; - visitRequestModel.createVisitPurpose(purposeData, (err, purposeId) => { - 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: '활성 방문 목적 조회 중 오류가 발생했습니다.' }); + } +}; + +exports.createVisitPurpose = async (req, res) => { + try { + if (!req.body.purpose_name) { + return res.status(400).json({ success: false, message: 'purpose_name은 필수 입력 항목입니다.' }); } - + 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: '방문 목적이 수정되었습니다.' - }); - }); + res.json({ success: true, message: '방문 목적이 수정되었습니다.' }); + } catch (err) { + logger.error('방문 목적 수정 오류:', err); + res.status(500).json({ success: false, 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: '방문 목적이 삭제되었습니다.' - }); - }); + res.json({ success: true, message: '방문 목적이 삭제되었습니다.' }); + } catch (err) { + logger.error('방문 목적 삭제 오류:', err); + res.status(500).json({ success: false, 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}는 필수 입력 항목입니다.` - }); - } - } - - visitRequestModel.createTrainingRecord(trainingData, (err, trainingId) => { - if (err) { - console.error('안전교육 기록 생성 오류:', err); - return res.status(500).json({ - success: false, - message: '안전교육 기록 생성 중 오류가 발생했습니다.', - error: err.message - }); - } - - // 안전교육 기록이 생성되면 출입 신청 상태를 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} 상태 변경 성공`); + 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}는 필수 입력 항목입니다.` }); } - - res.status(201).json({ - success: true, - message: '안전교육 기록이 생성되었습니다.', - data: { training_id: trainingId } - }); - }); - }); -}; - -/** - * 특정 출입 신청의 안전교육 기록 조회 - */ -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 - }); } - res.json({ + const trainingId = await visitRequestModel.createTrainingRecord(trainingData); + + // 안전교육 기록 생성 후 출입 신청 상태를 training_completed로 변경 + try { + await visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed'); + } catch (statusErr) { + logger.error('출입 신청 상태 업데이트 오류:', statusErr); + } + + res.status(201).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 - }); - } - - if (result.affectedRows === 0) { - return res.status(404).json({ - success: false, - message: '안전교육 기록을 찾을 수 없습니다.' - }); - } - - res.json({ - success: true, - message: '안전교육 기록이 수정되었습니다.' - }); - }); -}; - -/** - * 안전교육 완료 (서명 포함) - */ -exports.completeTraining = (req, res) => { - const trainingId = req.params.id; - const signatureData = req.body.signature_data; - - if (!signatureData) { - return res.status(400).json({ - success: false, - message: '서명 데이터가 필요합니다.' + message: '안전교육 기록이 생성되었습니다.', + data: { training_id: trainingId } }); + } catch (err) { + logger.error('안전교육 기록 생성 오류:', err); + res.status(500).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 - }); - } +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: '안전교육 기록 조회 중 오류가 발생했습니다.' }); + } +}; +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: '안전교육 기록 수정 중 오류가 발생했습니다.' }); + } +}; + +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: '서명 데이터가 필요합니다.' }); } - // 교육 완료 후 출입 신청 상태를 'training_completed'로 변경 - visitRequestModel.getTrainingRecordByRequestId(trainingId, (err, record) => { - if (err || !record) { - return res.json({ - success: true, - message: '안전교육이 완료되었습니다.' - }); + const result = await visitRequestModel.completeTraining(trainingId, signatureData); + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' }); + } + + // 교육 완료 후 출입 신청 상태 변경 + try { + const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId); + if (record) { + await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed'); } - - visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed', (err) => { - if (err) { - console.error('출입 신청 상태 업데이트 오류:', err); - } - - res.json({ - success: true, - message: '안전교육이 완료되었습니다.' - }); - }); - }); - }); -}; - -/** - * 안전교육 기록 목록 조회 - */ -exports.getTrainingRecords = (req, res) => { - 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 - }); + } catch (statusErr) { + logger.error('출입 신청 상태 업데이트 오류:', statusErr); } - res.json({ - success: true, - data: records - }); - }); + res.json({ success: true, message: '안전교육이 완료되었습니다.' }); + } catch (err) { + logger.error('안전교육 완료 처리 오류:', err); + res.status(500).json({ success: false, message: '안전교육 완료 처리 중 오류가 발생했습니다.' }); + } +}; + +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 + }; + const records = await visitRequestModel.getTrainingRecords(filters); + res.json({ success: true, data: records }); + } catch (err) { + logger.error('안전교육 기록 목록 조회 오류:', err); + res.status(500).json({ success: false, message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' }); + } }; diff --git a/system1-factory/api/controllers/workIssueController.js b/system1-factory/api/controllers/workIssueController.js index 54b472b..8abab9f 100644 --- a/system1-factory/api/controllers/workIssueController.js +++ b/system1-factory/api/controllers/workIssueController.js @@ -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) => { - const { type } = req.params; +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: '카테고리 조회 실패' }); + if (!['nonconformity', 'safety', 'facility'].includes(type)) { + return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' }); } + + const categories = await workIssueModel.getCategoriesByType(type); res.json({ success: true, data: categories }); - }); -}; - -/** - * 카테고리 생성 - */ -exports.createCategory = (req, res) => { - const { category_type, category_name, description, display_order } = req.body; - - if (!category_type || !category_name) { - return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' }); + } catch (err) { + logger.error('카테고리 조회 실패:', err); + res.status(500).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: '카테고리 생성 실패' }); - } - res.status(201).json({ - success: true, - message: '카테고리가 생성되었습니다.', - data: { category_id: categoryId } - }); - } - ); }; -/** - * 카테고리 수정 - */ -exports.updateCategory = (req, res) => { - const { id } = req.params; - const { category_name, description, display_order, is_active } = req.body; +exports.createCategory = async (req, res) => { + try { + const { category_type, category_name, description, display_order } = 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: '카테고리 수정 실패' }); - } - res.json({ success: true, message: '카테고리가 수정되었습니다.' }); + if (!category_type || !category_name) { + return res.status(400).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.deleteCategory = (req, res) => { - const { id } = req.params; +exports.updateCategory = async (req, res) => { + try { + const { id } = req.params; + const { category_name, description, display_order, is_active } = req.body; - workIssueModel.deleteCategory(id, (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 = async (req, res) => { + try { + const { id } = req.params; + 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) => { - const { categoryId } = req.params; - - workIssueModel.getItemsByCategory(categoryId, (err, items) => { - if (err) { - console.error('항목 조회 실패:', err); - return res.status(500).json({ success: false, error: '항목 조회 실패' }); - } +exports.getItemsByCategory = async (req, res) => { + try { + const { categoryId } = req.params; + const items = await workIssueModel.getItemsByCategory(categoryId); res.json({ success: true, data: items }); - }); -}; - -/** - * 모든 항목 조회 - */ -exports.getAllItems = (req, res) => { - workIssueModel.getAllItems((err, items) => { - if (err) { - console.error('항목 조회 실패:', err); - return res.status(500).json({ success: false, error: '항목 조회 실패' }); - } - res.json({ success: true, data: items }); - }); -}; - -/** - * 항목 생성 - */ -exports.createItem = (req, res) => { - 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와 항목명은 필수입니다.' }); + } catch (err) { + logger.error('항목 조회 실패:', err); + res.status(500).json({ success: false, error: '항목 조회 실패' }); } - - 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: '항목 생성 실패' }); - } - res.status(201).json({ - success: true, - message: '항목이 생성되었습니다.', - data: { item_id: itemId } - }); - } - ); }; -/** - * 항목 수정 - */ -exports.updateItem = (req, res) => { - 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: '항목 수정 실패' }); - } - res.json({ success: true, message: '항목이 수정되었습니다.' }); - } - ); +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.deleteItem = (req, res) => { - const { id } = req.params; +exports.createItem = async (req, res) => { + try { + const { category_id, item_name, description, severity, display_order } = req.body; - workIssueModel.deleteItem(id, (err, result) => { - if (err) { - console.error('항목 삭제 실패:', err); - return res.status(500).json({ success: false, error: '항목 삭제 실패' }); + if (!category_id || !item_name) { + return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' }); } + + 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 = async (req, res) => { + try { + const { id } = req.params; + const { item_name, description, severity, display_order, is_active } = req.body; + + 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 = async (req, res) => { + try { + const { id } = req.params; + 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( - { - 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); - } - ); - }); - } catch (itemErr) { - console.error('커스텀 항목 생성 실패:', itemErr); - return res.status(500).json({ success: false, error: '항목 저장 실패' }); - } + finalItemId = await workIssueModel.createItem({ + category_id: issue_category_id, + item_name: custom_item_name, + description: '사용자 직접 입력', + severity: 'medium', + display_order: 999 + }); } - // 사진 저장 (최대 5장) const photoPaths = { photo_path1: null, photo_path2: null, @@ -283,73 +217,56 @@ exports.createReport = async (req, res) => { ...photoPaths }; - workIssueModel.createReport(reportData, (err, reportId) => { - if (err) { - console.error('신고 생성 실패:', err); - return res.status(500).json({ success: false, error: '신고 생성 실패' }); - } - res.status(201).json({ - success: true, - message: '문제 신고가 등록되었습니다.', - data: { report_id: reportId } - }); + 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) => { - const filters = { - status: req.query.status, - category_type: req.query.category_type, - issue_category_id: req.query.issue_category_id, - factory_category_id: req.query.factory_category_id, - workplace_id: req.query.workplace_id, - assigned_user_id: req.query.assigned_user_id, - start_date: req.query.start_date, - end_date: req.query.end_date, - search: req.query.search, - limit: req.query.limit, - offset: req.query.offset - }; +exports.getAllReports = async (req, res) => { + try { + const filters = { + status: req.query.status, + category_type: req.query.category_type, + issue_category_id: req.query.issue_category_id, + factory_category_id: req.query.factory_category_id, + workplace_id: req.query.workplace_id, + assigned_user_id: req.query.assigned_user_id, + start_date: req.query.start_date, + end_date: req.query.end_date, + search: req.query.search, + limit: req.query.limit, + 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 userLevel = req.user.access_level; + if (!['admin', 'system', 'support_team'].includes(userLevel)) { + filters.reporter_id = req.user.user_id; } + + 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) => { - const { id } = req.params; - - workIssueModel.getReportById(id, (err, report) => { - if (err) { - console.error('신고 상세 조회 실패:', err); - return res.status(500).json({ success: false, error: '신고 상세 조회 실패' }); - } +exports.getReportById = async (req, res) => { + try { + const { id } = req.params; + 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,109 +277,84 @@ 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: '신고 조회 실패' }); - } - - 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); - - if (!isOwner && !isManager) { - return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' }); - } - - // 상태 확인: reported 상태에서만 수정 가능 (관리자 제외) - if (!isManager && report.status !== 'reported') { - return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' }); - } - - const { - factory_category_id, - workplace_id, - custom_location, - issue_category_id, - issue_item_id, - additional_description, - 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; - } - } - } - - const updateData = { - factory_category_id, - workplace_id, - custom_location, - issue_category_id, - issue_item_id, - additional_description, - ...photoPaths - }; - - workIssueModel.updateReport(id, updateData, req.user.user_id, (updateErr, result) => { - if (updateErr) { - console.error('신고 수정 실패:', updateErr); - return res.status(500).json({ success: false, error: '신고 수정 실패' }); - } - res.json({ success: true, message: '신고가 수정되었습니다.' }); - }); - }); - } catch (error) { - console.error('신고 수정 에러:', error); - res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); - } -}; - -/** - * 신고 삭제 - */ -exports.deleteReport = async (req, res) => { - 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); + + if (!isOwner && !isManager) { + return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' }); + } + + if (!isManager && report.status !== 'reported') { + return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' }); + } + + const { + factory_category_id, + workplace_id, + custom_location, + issue_category_id, + issue_item_id, + additional_description, + 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; + } + } + } + + const updateData = { + factory_category_id, + workplace_id, + custom_location, + issue_category_id, + issue_item_id, + additional_description, + ...photoPaths + }; + + await workIssueModel.updateReport(id, updateData, req.user.user_id); + res.json({ success: true, message: '신고가 수정되었습니다.' }); + } catch (err) { + logger.error('신고 수정 실패:', err); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); + } +}; + +exports.deleteReport = async (req, res) => { + try { + const { id } = req.params; + + 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,92 +363,74 @@ 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, - photos.photo_path4, photos.photo_path5, - photos.resolution_photo_path1, photos.resolution_photo_path2 - ].filter(Boolean); - await imageUploadService.deleteMultipleFiles(allPhotos); - } + if (photos) { + const allPhotos = [ + photos.photo_path1, photos.photo_path2, photos.photo_path3, + photos.photo_path4, photos.photo_path5, + photos.resolution_photo_path1, photos.resolution_photo_path2 + ].filter(Boolean); + await imageUploadService.deleteMultipleFiles(allPhotos); + } - res.json({ success: true, message: '신고가 삭제되었습니다.' }); - }); - }); + res.json({ success: true, message: '신고가 삭제되었습니다.' }); + } catch (err) { + logger.error('신고 삭제 실패:', err); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); + } }; // ==================== 상태 관리 ==================== -/** - * 신고 접수 - */ -exports.receiveReport = (req, res) => { - 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 || '신고 접수 실패' }); - } +exports.receiveReport = async (req, res) => { + try { + const { id } = req.params; + await workIssueModel.receiveReport(id, req.user.user_id); res.json({ success: true, message: '신고가 접수되었습니다.' }); - }); -}; - -/** - * 담당자 배정 - */ -exports.assignReport = (req, res) => { - const { id } = req.params; - const { assigned_department, assigned_user_id } = req.body; - - if (!assigned_user_id) { - return res.status(400).json({ success: false, error: '담당자는 필수입니다.' }); + } catch (err) { + logger.error('신고 접수 실패:', err); + res.status(400).json({ success: false, error: err.message || '신고 접수 실패' }); } +}; - 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 || '담당자 배정 실패' }); +exports.assignReport = async (req, res) => { + try { + const { id } = req.params; + const { assigned_department, assigned_user_id } = req.body; + + if (!assigned_user_id) { + return res.status(400).json({ success: false, error: '담당자는 필수입니다.' }); } + + await workIssueModel.assignReport(id, { + assigned_department, + assigned_user_id, + assigned_by: req.user.user_id + }); res.json({ success: true, message: '담당자가 배정되었습니다.' }); - }); + } catch (err) { + logger.error('담당자 배정 실패:', err); + res.status(400).json({ success: false, error: err.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 || '처리 시작 실패' }); - } +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 || '처리 시작 실패' }); + } }; -/** - * 처리 완료 - */ 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: '서버 오류가 발생했습니다.' }); + res.json({ success: true, message: '처리가 완료되었습니다.' }); + } catch (err) { + logger.error('처리 완료 실패:', err); + res.status(400).json({ success: false, error: err.message || '처리 완료 실패' }); } }; -/** - * 신고 종료 - */ -exports.closeReport = (req, res) => { - 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 || '신고 종료 실패' }); - } +exports.closeReport = async (req, res) => { + try { + const { id } = req.params; + 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) => { - const { id } = req.params; - - workIssueModel.getStatusLogs(id, (err, logs) => { - if (err) { - console.error('상태 이력 조회 실패:', err); - return res.status(500).json({ success: false, error: '상태 이력 조회 실패' }); - } +exports.getStatusLogs = async (req, res) => { + try { + const { id } = req.params; + 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) => { - 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: '통계 조회 실패' }); - } +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 + }; + 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) => { - 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: '통계 조회 실패' }); - } +exports.getStatsByCategory = async (req, res) => { + try { + const filters = { + start_date: req.query.start_date, + end_date: req.query.end_date + }; + 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) => { - 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: '통계 조회 실패' }); - } +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 + }; + const stats = await workIssueModel.getStatsByWorkplace(filters); res.json({ success: true, data: stats }); - }); + } catch (err) { + logger.error('작업장별 통계 조회 실패:', err); + res.status(500).json({ success: false, error: '통계 조회 실패' }); + } }; diff --git a/system1-factory/api/controllers/workplaceController.js b/system1-factory/api/controllers/workplaceController.js index b384529..6f3a683 100644 --- a/system1-factory/api/controllers/workplaceController.js +++ b/system1-factory/api/controllers/workplaceController.js @@ -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 }); diff --git a/system1-factory/api/models/dailyIssueReportModel.js b/system1-factory/api/models/dailyIssueReportModel.js index 8d7f119..103ad14 100644 --- a/system1-factory/api/models/dailyIssueReportModel.js +++ b/system1-factory/api/models/dailyIssueReportModel.js @@ -2,8 +2,6 @@ const { getDb } = require('../dbPool'); /** * 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다. - * @param {Array} reports - 생성할 보고서 데이터 배열 - * @returns {Promise>} - 삽입된 ID 배열 */ const createMany = async (reports) => { const db = await getDb(); @@ -13,7 +11,7 @@ const createMany = async (reports) => { const insertedIds = []; const sql = ` - INSERT INTO DailyIssueReports + INSERT INTO DailyIssueReports (date, worker_id, project_id, start_time, end_time, issue_type_id) VALUES (?, ?, ?, ?, ?, ?) `; @@ -36,119 +34,71 @@ const createMany = async (reports) => { }; /** - * 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반) + * 2. 특정 날짜의 전체 이슈 목록 조회 */ const getAllByDate = async (date) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT - d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time, - t.category, t.subcategory, d.description - FROM DailyIssueReports d - LEFT JOIN workers w ON d.worker_id = w.worker_id - LEFT JOIN projects p ON d.project_id = p.project_id - LEFT JOIN IssueTypes t ON d.issue_type_id = t.issue_type_id - WHERE d.date = ? - ORDER BY d.start_time ASC`, - [date] - ); - return rows; - } catch (err) { - console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err); - throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.'); - } + const db = await getDb(); + const [rows] = await db.query( + `SELECT + d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time, + t.category, t.subcategory, d.description + FROM DailyIssueReports d + LEFT JOIN workers w ON d.worker_id = w.worker_id + LEFT JOIN projects p ON d.project_id = p.project_id + LEFT JOIN IssueTypes t ON d.issue_type_id = t.issue_type_id + WHERE d.date = ? + ORDER BY d.start_time ASC`, + [date] + ); + return rows; }; /** - * 3. 단일 조회 (선택사항: 컨트롤러에서 사용 중) + * 3. 단일 조회 */ -const getById = async (id, callback) => { - try { - 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); - } +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]); + return rows[0]; }; /** * 4. 수정 */ -const update = async (id, data, callback) => { - try { - const db = await getDb(); +const update = async (id, data) => { + const db = await getDb(); - const fields = []; - const values = []; + const fields = []; + const values = []; - for (const key in data) { - fields.push(`${key} = ?`); - values.push(data[key]); - } - - values.push(id); // 마지막에 id - - const [result] = await db.query( - `UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`, - values - ); - - callback(null, result.affectedRows); - } catch (err) { - callback(err); + for (const key in data) { + fields.push(`${key} = ?`); + values.push(data[key]); } + + values.push(id); + + const [result] = await db.query( + `UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`, + values + ); + + 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('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.'); - } + const db = await getDb(); + const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]); + return result.affectedRows; }; -// 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, -}; \ No newline at end of file + remove +}; diff --git a/system1-factory/api/models/dailyWorkReportModel.js b/system1-factory/api/models/dailyWorkReportModel.js index 8cf266a..04fb63d 100644 --- a/system1-factory/api/models/dailyWorkReportModel.js +++ b/system1-factory/api/models/dailyWorkReportModel.js @@ -2,59 +2,43 @@ const { getDb } = require('../dbPool'); /** - * 📋 마스터 데이터 조회 함수들 + * 마스터 데이터 조회 함수들 */ -const getAllWorkTypes = async (callback) => { - try { - 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); - } +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'); + return rows; }; -const getAllWorkStatusTypes = async (callback) => { - try { - 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); - } +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'); + return rows; }; -const getAllErrorTypes = async (callback) => { - try { - const db = await getDb(); - // issue_report_items에서 부적합(nonconformity) 타입의 항목만 조회 - const [rows] = await db.query(` - SELECT - iri.item_id as id, - iri.item_name as name, - iri.description, - iri.severity, - irc.category_name as category, - iri.display_order, - iri.created_at - FROM issue_report_items iri - INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id - 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); - } +const getAllErrorTypes = async () => { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + iri.item_id as id, + iri.item_name as name, + iri.description, + iri.severity, + irc.category_name as category, + iri.display_order, + iri.created_at + FROM issue_report_items iri + INNER JOIN issue_report_categories irc ON iri.category_id = irc.category_id + WHERE irc.category_type = 'nonconformity' AND iri.is_active = TRUE + ORDER BY irc.display_order, iri.display_order, iri.item_name ASC + `); + 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,17 +57,16 @@ 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; const [insertResult] = await conn.query( - `INSERT INTO daily_work_reports - (report_date, worker_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_by, created_at) + `INSERT INTO daily_work_reports + (report_date, worker_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())`, [report_date, worker_id, project_id, work_type_id, work_status_id || 1, error_type_id || null, work_hours, created_by] ); @@ -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,13 +90,13 @@ 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 - (action, report_id, new_values, changed_by, change_reason, created_at) + `INSERT INTO work_report_audit_log + (action, report_id, new_values, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, ?, NOW())`, [ 'ADD_ACCUMULATE', @@ -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,104 +140,88 @@ 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 db = await getDb(); +const getMyAccumulatedHours = async (date, worker_id, created_by) => { + const db = await getDb(); - const sql = ` - SELECT - SUM(work_hours) as my_total_hours, - COUNT(*) as my_entry_count, - GROUP_CONCAT( - CONCAT(p.project_name, ':', work_hours, 'h') - ORDER BY created_at - ) as my_entries - FROM daily_work_reports dwr - LEFT JOIN projects p ON dwr.project_id = p.project_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? AND dwr.created_by = ? - `; + const sql = ` + SELECT + SUM(work_hours) as my_total_hours, + COUNT(*) as my_entry_count, + GROUP_CONCAT( + CONCAT(p.project_name, ':', work_hours, 'h') + ORDER BY created_at + ) as my_entries + FROM daily_work_reports dwr + LEFT JOIN projects p ON dwr.project_id = p.project_id + WHERE dwr.report_date = ? AND dwr.worker_id = ? AND dwr.created_by = ? + `; - const [rows] = await db.query(sql, [date, worker_id, created_by]); - callback(null, rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null }); - } catch (err) { - console.error('개인 누적 현황 조회 오류:', err); - callback(err); - } + const [rows] = await db.query(sql, [date, worker_id, created_by]); + return rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null }; }; /** - * 📊 누적 현황 조회 - 날짜+작업자별 (모든 기여자) + * 누적 현황 조회 - 날짜+작업자별 (모든 기여자) */ -const getAccumulatedReportsByDate = async (date, worker_id, callback) => { - try { - const db = await getDb(); +const getAccumulatedReportsByDate = async (date, worker_id) => { + const db = await getDb(); - const sql = getSelectQuery() + ` - WHERE dwr.report_date = ? AND dwr.worker_id = ? - ORDER BY dwr.created_by, dwr.created_at ASC - `; + const sql = getSelectQuery() + ` + WHERE dwr.report_date = ? AND dwr.worker_id = ? + ORDER BY dwr.created_by, dwr.created_at ASC + `; - const [rows] = await db.query(sql, [date, worker_id]); - callback(null, rows); - } catch (err) { - console.error('누적 현황 조회 오류:', err); - callback(err); - } + const [rows] = await db.query(sql, [date, worker_id]); + return rows; }; /** - * 📊 기여자별 요약 조회 + * 기여자별 요약 조회 */ -const getContributorsByDate = async (date, worker_id, callback) => { - try { - const db = await getDb(); +const getContributorsByDate = async (date, worker_id) => { + const db = await getDb(); - const sql = ` - SELECT - dwr.created_by, - u.name as created_by_name, - COUNT(*) as entry_count, - SUM(dwr.work_hours) as total_hours, - MIN(dwr.created_at) as first_entry, - MAX(dwr.created_at) as last_entry, - GROUP_CONCAT( - CONCAT(p.project_name, ':', dwr.work_hours, 'h') - ORDER BY dwr.created_at SEPARATOR ', ' - ) as entry_details - FROM daily_work_reports dwr - LEFT JOIN users u ON dwr.created_by = u.user_id - LEFT JOIN projects p ON dwr.project_id = p.project_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? - GROUP BY dwr.created_by - ORDER BY total_hours DESC, first_entry ASC - `; + const sql = ` + SELECT + dwr.created_by, + u.name as created_by_name, + COUNT(*) as entry_count, + SUM(dwr.work_hours) as total_hours, + MIN(dwr.created_at) as first_entry, + MAX(dwr.created_at) as last_entry, + GROUP_CONCAT( + CONCAT(p.project_name, ':', dwr.work_hours, 'h') + ORDER BY dwr.created_at SEPARATOR ', ' + ) as entry_details + FROM daily_work_reports dwr + LEFT JOIN users u ON dwr.created_by = u.user_id + LEFT JOIN projects p ON dwr.project_id = p.project_id + WHERE dwr.report_date = ? AND dwr.worker_id = ? + GROUP BY dwr.created_by + ORDER BY total_hours DESC, first_entry ASC + `; - const [rows] = await db.query(sql, [date, worker_id]); - callback(null, rows); - } catch (err) { - console.error('기여자별 요약 조회 오류:', err); - callback(err); - } + const [rows] = await db.query(sql, [date, worker_id]); + 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,336 +311,279 @@ const getSelectQuery = () => ` /** * 7. ID로 작업보고서 조회 */ -const getById = async (id, callback) => { - try { - 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); - } +const getById = async (id) => { + const db = await getDb(); + const sql = getSelectQuery() + 'WHERE dwr.id = ?'; + const [rows] = await db.query(sql, [id]); + return rows[0] || null; }; /** * 8. 일일 작업보고서 조회 (날짜별) */ -const getByDate = async (date, callback) => { - try { - 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); - } +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]); + return rows; }; /** * 9. 일일 작업보고서 조회 (날짜 + 작성자별) */ -const getByDateAndCreator = async (date, created_by, callback) => { - try { - 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); - } +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]); + return rows; }; /** * 10. 일일 작업보고서 조회 (작업자별) */ -const getByWorker = async (worker_id, callback) => { - try { - 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); - } +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]); + return rows; }; /** * 11. 일일 작업보고서 조회 (날짜 + 작업자) */ -const getByDateAndWorker = async (date, worker_id, callback) => { - try { - 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); - } +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]); + return rows; }; /** * 12. 기간별 조회 */ -const getByRange = async (start_date, end_date, callback) => { - try { - 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); - } +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]); + 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(); + const db = await getDb(); - // 조건 구성 - let whereConditions = ['dwr.report_date BETWEEN ? AND ?']; - let queryParams = [start_date, end_date]; + // 조건 구성 + let whereConditions = ['dwr.report_date BETWEEN ? AND ?']; + let queryParams = [start_date, end_date]; - if (worker_id) { - whereConditions.push('dwr.worker_id = ?'); - queryParams.push(worker_id); - } - - if (project_id) { - whereConditions.push('dwr.project_id = ?'); - queryParams.push(project_id); - } - - if (work_status_id) { - whereConditions.push('dwr.work_status_id = ?'); - queryParams.push(work_status_id); - } - - if (created_by) { - whereConditions.push('dwr.created_by = ?'); - queryParams.push(created_by); - } - - const whereClause = whereConditions.join(' AND '); - - // 총 개수 조회 - const countQuery = ` - SELECT COUNT(*) as total - FROM daily_work_reports dwr - WHERE ${whereClause} - `; - const [countResult] = await db.query(countQuery, queryParams); - const total = countResult[0].total; - - // 데이터 조회 (JOIN 포함) - const offset = (page - 1) * limit; - const dataQuery = getSelectQuery() + ` - WHERE ${whereClause} - ORDER BY dwr.report_date DESC, w.worker_name ASC, dwr.created_at DESC - LIMIT ? OFFSET ? - `; - - const dataParams = [...queryParams, limit, offset]; - const [rows] = await db.query(dataQuery, dataParams); - - callback(null, { reports: rows, total }); - } catch (err) { - console.error('상세 검색 오류:', err); - callback(err); + if (worker_id) { + whereConditions.push('dwr.worker_id = ?'); + queryParams.push(worker_id); } + + if (project_id) { + whereConditions.push('dwr.project_id = ?'); + queryParams.push(project_id); + } + + if (work_status_id) { + whereConditions.push('dwr.work_status_id = ?'); + queryParams.push(work_status_id); + } + + if (created_by) { + whereConditions.push('dwr.created_by = ?'); + queryParams.push(created_by); + } + + const whereClause = whereConditions.join(' AND '); + + // 총 개수 조회 + const countQuery = ` + SELECT COUNT(*) as total + FROM daily_work_reports dwr + WHERE ${whereClause} + `; + const [countResult] = await db.query(countQuery, queryParams); + const total = countResult[0].total; + + // 데이터 조회 (JOIN 포함) + const offset = (page - 1) * limit; + const dataQuery = getSelectQuery() + ` + WHERE ${whereClause} + ORDER BY dwr.report_date DESC, w.worker_name ASC, dwr.created_at DESC + LIMIT ? OFFSET ? + `; + + const dataParams = [...queryParams, limit, offset]; + const [rows] = await db.query(dataQuery, dataParams); + + return { reports: rows, total }; }; /** * 14. 일일 근무 요약 조회 (날짜별) */ -const getSummaryByDate = async (date, callback) => { - try { - const db = await getDb(); +const getSummaryByDate = async (date) => { + const db = await getDb(); - const sql = ` - SELECT - dwr.worker_id, - w.worker_name, - dwr.report_date, - SUM(dwr.work_hours) as total_hours, - COUNT(*) as work_entries_count, - SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count - FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id - WHERE dwr.report_date = ? - GROUP BY dwr.worker_id, dwr.report_date - ORDER BY w.worker_name ASC - `; - const [rows] = await db.query(sql, [date]); - callback(null, rows); - } catch (err) { - console.error('일일 근무 요약 조회 오류:', err); - callback(err); - } + const sql = ` + SELECT + dwr.worker_id, + w.worker_name, + dwr.report_date, + SUM(dwr.work_hours) as total_hours, + COUNT(*) as work_entries_count, + SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count + FROM daily_work_reports dwr + LEFT JOIN workers w ON dwr.worker_id = w.worker_id + WHERE dwr.report_date = ? + GROUP BY dwr.worker_id, dwr.report_date + ORDER BY w.worker_name ASC + `; + const [rows] = await db.query(sql, [date]); + return rows; }; /** * 15. 일일 근무 요약 조회 (작업자별) */ -const getSummaryByWorker = async (worker_id, callback) => { - try { - const db = await getDb(); +const getSummaryByWorker = async (worker_id) => { + const db = await getDb(); - const sql = ` - SELECT - dwr.report_date, - dwr.worker_id, - w.worker_name, - SUM(dwr.work_hours) as total_hours, - COUNT(*) as work_entries_count, - SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count - FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id - WHERE dwr.worker_id = ? - GROUP BY dwr.report_date, dwr.worker_id - ORDER BY dwr.report_date DESC - `; - const [rows] = await db.query(sql, [worker_id]); - callback(null, rows); - } catch (err) { - console.error('작업자별 근무 요약 조회 오류:', err); - callback(err); - } + const sql = ` + SELECT + dwr.report_date, + dwr.worker_id, + w.worker_name, + SUM(dwr.work_hours) as total_hours, + COUNT(*) as work_entries_count, + SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count + FROM daily_work_reports dwr + LEFT JOIN workers w ON dwr.worker_id = w.worker_id + WHERE dwr.worker_id = ? + GROUP BY dwr.report_date, dwr.worker_id + ORDER BY dwr.report_date DESC + `; + const [rows] = await db.query(sql, [worker_id]); + return rows; }; /** * 16. 월간 요약 */ -const getMonthlySummary = async (year, month, callback) => { - try { - 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`; +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`; - const sql = ` - SELECT - dwr.report_date, - dwr.worker_id, - w.worker_name, - SUM(dwr.work_hours) as total_work_hours, - COUNT(DISTINCT dwr.project_id) as project_count, - COUNT(*) as work_entries_count, - SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count, - GROUP_CONCAT(DISTINCT p.project_name ORDER BY p.project_name) as projects, - GROUP_CONCAT(DISTINCT wt.name ORDER BY wt.name) as work_types - FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id - LEFT JOIN projects p ON dwr.project_id = p.project_id - LEFT JOIN work_types wt ON dwr.work_type_id = wt.id - WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY dwr.report_date, dwr.worker_id - 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); - } + const sql = ` + SELECT + dwr.report_date, + dwr.worker_id, + w.worker_name, + SUM(dwr.work_hours) as total_work_hours, + COUNT(DISTINCT dwr.project_id) as project_count, + COUNT(*) as work_entries_count, + SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count, + GROUP_CONCAT(DISTINCT p.project_name ORDER BY p.project_name) as projects, + GROUP_CONCAT(DISTINCT wt.name ORDER BY wt.name) as work_types + FROM daily_work_reports dwr + LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN projects p ON dwr.project_id = p.project_id + LEFT JOIN work_types wt ON dwr.work_type_id = wt.id + WHERE dwr.report_date BETWEEN ? AND ? + GROUP BY dwr.report_date, dwr.worker_id + ORDER BY dwr.report_date DESC, w.worker_name ASC + `; + const [rows] = await db.query(sql, [start, end]); + return rows; }; /** * 17. 작업보고서 수정 */ -const updateById = async (id, updateData, callback) => { - try { - const db = await getDb(); +const updateById = async (id, updateData) => { + const db = await getDb(); - const setFields = []; - const values = []; + const setFields = []; + const values = []; - if (updateData.work_hours !== undefined) { - setFields.push('work_hours = ?'); - values.push(updateData.work_hours); - } - - if (updateData.work_status_id !== undefined) { - setFields.push('work_status_id = ?'); - values.push(updateData.work_status_id); - } - - if (updateData.error_type_id !== undefined) { - setFields.push('error_type_id = ?'); - values.push(updateData.error_type_id); - } - - if (updateData.project_id !== undefined) { - setFields.push('project_id = ?'); - values.push(updateData.project_id); - } - - if (updateData.work_type_id !== undefined) { - setFields.push('work_type_id = ?'); - values.push(updateData.work_type_id); - } - - setFields.push('updated_at = NOW()'); - - if (updateData.updated_by) { - setFields.push('updated_by = ?'); - values.push(updateData.updated_by); - } - - values.push(id); - - 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]); - if (targetReport.length > 0) { - const { worker_id, report_date } = targetReport[0]; - const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); - } - } catch (syncErr) { - console.error('근태 기록 동기화 실패 (Update):', syncErr); - } - - callback(null, result.affectedRows); - } catch (err) { - console.error('작업보고서 수정 오류:', err); - callback(err); + if (updateData.work_hours !== undefined) { + setFields.push('work_hours = ?'); + values.push(updateData.work_hours); } + + if (updateData.work_status_id !== undefined) { + setFields.push('work_status_id = ?'); + values.push(updateData.work_status_id); + } + + if (updateData.error_type_id !== undefined) { + setFields.push('error_type_id = ?'); + values.push(updateData.error_type_id); + } + + if (updateData.project_id !== undefined) { + setFields.push('project_id = ?'); + values.push(updateData.project_id); + } + + if (updateData.work_type_id !== undefined) { + setFields.push('work_type_id = ?'); + values.push(updateData.work_type_id); + } + + setFields.push('updated_at = NOW()'); + + if (updateData.updated_by) { + setFields.push('updated_by = ?'); + values.push(updateData.updated_by); + } + + values.push(id); + + 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]); + if (targetReport.length > 0) { + const { worker_id, report_date } = targetReport[0]; + const AttendanceModel = require('./attendanceModel'); + await AttendanceModel.syncWithWorkReports(worker_id, report_date); + } + } catch (syncErr) { + console.error('근태 기록 동기화 실패 (Update):', syncErr); + } + + return result.affectedRows; }; /** * 18. 특정 작업보고서 삭제 */ -const removeById = async (id, deletedBy, callback) => { +const removeById = async (id, deletedBy) => { const db = await getDb(); const conn = await db.getConnection(); @@ -696,8 +600,8 @@ const removeById = async (id, deletedBy, callback) => { if (reportInfo.length > 0 && deletedBy) { try { await conn.query( - `INSERT INTO work_report_audit_log - (action, report_id, old_values, changed_by, change_reason, created_at) + `INSERT INTO work_report_audit_log + (action, report_id, old_values, changed_by, change_reason, created_at) VALUES ('DELETE', ?, ?, ?, 'Manual deletion', NOW())`, [id, JSON.stringify(reportInfo[0]), deletedBy] ); @@ -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(); @@ -756,8 +658,8 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => { if (reportInfos.length > 0 && deletedBy) { try { await conn.query( - `INSERT INTO work_report_audit_log - (action, old_values, changed_by, change_reason, created_at) + `INSERT INTO work_report_audit_log + (action, old_values, changed_by, change_reason, created_at) VALUES ('DELETE_BATCH', ?, ?, 'Batch deletion by date and worker', NOW())`, [JSON.stringify({ deleted_reports: reportInfos, count: reportInfos.length }), deletedBy] ); @@ -768,7 +670,6 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy, callback) => { await conn.commit(); - // [Sync] 근태 기록 동기화 try { const AttendanceModel = require('./attendanceModel'); @@ -777,58 +678,52 @@ 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 db = await getDb(); - const overallSql = ` - SELECT - COUNT(*) as total_reports, - SUM(work_hours) as total_hours, - COUNT(DISTINCT worker_id) as unique_workers, - COUNT(DISTINCT project_id) as unique_projects - FROM daily_work_reports - WHERE report_date BETWEEN ? AND ? - `; - const [overallRows] = await db.query(overallSql, [start_date, end_date]); + const overallSql = ` + SELECT + COUNT(*) as total_reports, + SUM(work_hours) as total_hours, + COUNT(DISTINCT worker_id) as unique_workers, + COUNT(DISTINCT project_id) as unique_projects + FROM daily_work_reports + WHERE report_date BETWEEN ? AND ? + `; + const [overallRows] = await db.query(overallSql, [start_date, end_date]); - const dailyStatsSql = ` - SELECT - report_date, - SUM(work_hours) as daily_hours, - COUNT(DISTINCT worker_id) as daily_workers - FROM daily_work_reports - WHERE report_date BETWEEN ? AND ? - GROUP BY report_date - ORDER BY report_date DESC - `; - const [dailyStats] = await db.query(dailyStatsSql, [start_date, end_date]); + const dailyStatsSql = ` + SELECT + report_date, + SUM(work_hours) as daily_hours, + COUNT(DISTINCT worker_id) as daily_workers + FROM daily_work_reports + WHERE report_date BETWEEN ? AND ? + GROUP BY report_date + ORDER BY report_date DESC + `; + const [dailyStats] = await db.query(dailyStatsSql, [start_date, end_date]); - return { - overall: overallRows[0], - daily_breakdown: dailyStats - }; - } catch (err) { - console.error('통계 조회 오류:', err); - throw new Error('데이터베이스에서 통계 정보를 조회하는 중 오류가 발생했습니다.'); - } + return { + overall: overallRows[0], + daily_breakdown: dailyStats + }; }; /** - * [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다. (Promise 기반) + * [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다. * @param {object} modelData - 서비스 레이어에서 전달된 데이터 * @returns {Promise} 삽입된 항목의 ID 배열과 개수 */ @@ -840,8 +735,8 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => { const insertedIds = []; const sql = ` - INSERT INTO daily_work_reports - (report_date, worker_id, project_id, work_type_id, work_hours, work_status_id, error_type_id, created_by) + INSERT INTO daily_work_reports + (report_date, worker_id, project_id, work_type_id, work_hours, work_status_id, error_type_id, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; @@ -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} 조회된 작업 보고서 배열 */ @@ -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('데이터베이스에서 작업 보고서를 조회하는 중 오류가 발생했습니다.'); - } + const [rows] = await db.query(sql, queryParams); + return rows; }; /** - * [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다. (Promise 기반) + * [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다. * @param {string} reportId - 수정할 보고서의 ID * @param {object} updateData - 수정할 필드와 값 * @returns {Promise} 영향을 받은 행의 수 @@ -1011,28 +896,23 @@ const updateReportById = async (reportId, updateData) => { const sql = `UPDATE daily_work_reports SET ${setClauses.join(', ')} WHERE id = ?`; - try { - const [result] = await db.query(sql, queryParams); + const [result] = await db.query(sql, queryParams); - // [Sync] 근태 기록 동기화 - if (targetInfo) { - try { - const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(targetInfo.worker_id, targetInfo.report_date); - } catch (syncErr) { - console.error('근태 기록 동기화 실패 (V2 Update):', syncErr); - } + // [Sync] 근태 기록 동기화 + if (targetInfo) { + try { + const AttendanceModel = require('./attendanceModel'); + await AttendanceModel.syncWithWorkReports(targetInfo.worker_id, targetInfo.report_date); + } catch (syncErr) { + console.error('근태 기록 동기화 실패 (V2 Update):', syncErr); } - - return result.affectedRows; - } catch (err) { - console.error(`[Model] 작업 보고서 수정 오류 (id: ${reportId}):`, err); - throw new Error('데이터베이스에서 작업 보고서를 수정하는 중 오류가 발생했습니다.'); } + + return result.affectedRows; }; /** - * [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다. (Promise 기반) + * [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다. * @param {string} reportId - 삭제할 보고서 ID * @param {number} deletedByUserId - 삭제를 수행하는 사용자 ID * @returns {Promise} 영향을 받은 행의 수 @@ -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 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); - } +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] + ); + return { id: result.insertId, ...data }; }; /** - * ✏️ 작업 유형 수정 + * 작업 유형 수정 */ -const updateWorkType = async (id, data, callback) => { - try { - 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); - } +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] + ); + return result; }; /** - * 🗑️ 작업 유형 삭제 + * 작업 유형 삭제 */ -const deleteWorkType = async (id, callback) => { - try { - 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); - } +const deleteWorkType = async (id) => { + const db = await getDb(); + const [result] = await db.query('DELETE FROM work_types WHERE id = ?', [id]); + return result; }; /** - * 📝 작업 상태 생성 + * 작업 상태 생성 */ -const createWorkStatus = async (data, callback) => { - try { - 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); - } +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] + ); + return { id: result.insertId, ...data }; }; /** - * ✏️ 작업 상태 수정 + * 작업 상태 수정 */ -const updateWorkStatus = async (id, data, callback) => { - try { - 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); - } +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] + ); + return result; }; /** - * 🗑️ 작업 상태 삭제 + * 작업 상태 삭제 */ -const deleteWorkStatus = async (id, callback) => { - try { - 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); - } +const deleteWorkStatus = async (id) => { + const db = await getDb(); + const [result] = await db.query('DELETE FROM work_status_types WHERE id = ?', [id]); + return result; }; /** - * 📝 오류 유형 생성 + * 오류 유형 생성 */ -const createErrorType = async (data, callback) => { - try { - 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); - } +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'] + ); + return { id: result.insertId, ...data }; }; /** - * ✏️ 오류 유형 수정 + * 오류 유형 수정 */ -const updateErrorType = async (id, data, callback) => { - try { - 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); - } +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] + ); + return result; }; /** - * 🗑️ 오류 유형 삭제 + * 오류 유형 삭제 */ -const deleteErrorType = async (id, callback) => { - try { - 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); - } +const deleteErrorType = async (id) => { + const db = await getDb(); + const [result] = await db.query('DELETE FROM error_types WHERE id = ?', [id]); + 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, @@ -1385,4 +1219,4 @@ module.exports = { updateById, removeById, removeByDateAndWorker, -}; \ No newline at end of file +}; diff --git a/system1-factory/api/models/equipmentModel.js b/system1-factory/api/models/equipmentModel.js index ec0bd15..0cd3a2d 100644 --- a/system1-factory/api/models/equipmentModel.js +++ b/system1-factory/api/models/equipmentModel.js @@ -4,459 +4,374 @@ const notificationModel = require('./notificationModel'); const EquipmentModel = { // CREATE - 설비 생성 - create: async (equipmentData, callback) => { - try { - const db = await getDb(); - const query = ` - INSERT INTO equipments ( - 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 - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `; + create: async (equipmentData) => { + const db = await getDb(); + const query = ` + INSERT INTO equipments ( + 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 + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `; - const values = [ - equipmentData.equipment_code, - equipmentData.equipment_name, - equipmentData.equipment_type || null, - equipmentData.model_name || null, - equipmentData.manufacturer || null, - equipmentData.supplier || null, - equipmentData.purchase_price || null, - equipmentData.installation_date || null, - equipmentData.serial_number || null, - equipmentData.specifications || null, - equipmentData.status || 'active', - equipmentData.notes || null, - equipmentData.workplace_id || null, - equipmentData.map_x_percent || null, - equipmentData.map_y_percent || null, - equipmentData.map_width_percent || null, - equipmentData.map_height_percent || null - ]; + const values = [ + equipmentData.equipment_code, + equipmentData.equipment_name, + equipmentData.equipment_type || null, + equipmentData.model_name || null, + equipmentData.manufacturer || null, + equipmentData.supplier || null, + equipmentData.purchase_price || null, + equipmentData.installation_date || null, + equipmentData.serial_number || null, + equipmentData.specifications || null, + equipmentData.status || 'active', + equipmentData.notes || null, + equipmentData.workplace_id || null, + equipmentData.map_x_percent || null, + equipmentData.map_y_percent || null, + equipmentData.map_width_percent || null, + equipmentData.map_height_percent || null + ]; - const [result] = await db.query(query, values); - callback(null, { - equipment_id: result.insertId, - ...equipmentData - }); - } catch (error) { - callback(error); - } + const [result] = await db.query(query, values); + return { + equipment_id: result.insertId, + ...equipmentData + }; }, // READ ALL - 모든 설비 조회 (필터링 옵션 포함) - getAll: async (filters, callback) => { - try { - const db = await getDb(); - let 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 1=1 - `; + getAll: async (filters) => { + const db = await getDb(); + let 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 1=1 + `; - const values = []; + 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}%`; - values.push(searchTerm, searchTerm); - } - - query += ' ORDER BY e.equipment_code ASC'; - - const [rows] = await db.query(query, values); - callback(null, rows); - } catch (error) { - callback(error); + 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}%`; + values.push(searchTerm, searchTerm); + } + + query += ' ORDER BY e.equipment_code ASC'; + + const [rows] = await db.query(query, values); + return rows; }, // READ ONE - 특정 설비 조회 - getById: async (equipmentId, callback) => { - try { - const db = await getDb(); - const 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); - } + getById: async (equipmentId) => { + const db = await getDb(); + 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 = ?`, + [equipmentId] + ); + return rows[0]; }, // READ BY WORKPLACE - 특정 작업장의 설비 조회 - getByWorkplace: async (workplaceId, callback) => { - try { - const db = await getDb(); - const 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); - } + getByWorkplace: async (workplaceId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT e.* + FROM equipments e + WHERE e.workplace_id = ? + ORDER BY e.equipment_code ASC`, + [workplaceId] + ); + return rows; }, // READ ACTIVE - 활성 설비만 조회 - getActive: async (callback) => { - try { - const db = await getDb(); - const 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.status = 'active' - ORDER BY e.equipment_code ASC - `; - - const [rows] = await db.query(query); - callback(null, rows); - } catch (error) { - callback(error); - } + getActive: async () => { + const db = await getDb(); + 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.status = 'active' + ORDER BY e.equipment_code ASC` + ); + return rows; }, // UPDATE - 설비 수정 - update: async (equipmentId, equipmentData, callback) => { - try { - 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 = ? - `; + update: async (equipmentId, equipmentData) => { + const db = await getDb(); + const values = [ + equipmentData.equipment_code, + equipmentData.equipment_name, + equipmentData.equipment_type || null, + equipmentData.model_name || null, + equipmentData.manufacturer || null, + equipmentData.supplier || null, + equipmentData.purchase_price || null, + equipmentData.installation_date || null, + equipmentData.serial_number || null, + equipmentData.specifications || null, + equipmentData.status || 'active', + equipmentData.notes || null, + equipmentData.workplace_id || null, + equipmentData.map_x_percent || null, + equipmentData.map_y_percent || null, + equipmentData.map_width_percent || null, + equipmentData.map_height_percent || null, + equipmentId + ]; - const values = [ - equipmentData.equipment_code, - equipmentData.equipment_name, - equipmentData.equipment_type || null, - equipmentData.model_name || null, - equipmentData.manufacturer || null, - equipmentData.supplier || null, - equipmentData.purchase_price || null, - equipmentData.installation_date || null, - equipmentData.serial_number || null, - equipmentData.specifications || null, - equipmentData.status || 'active', - equipmentData.notes || null, - equipmentData.workplace_id || null, - equipmentData.map_x_percent || null, - equipmentData.map_y_percent || null, - equipmentData.map_width_percent || null, - equipmentData.map_height_percent || null, - equipmentId - ]; + 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 + ); - const [result] = await db.query(query, values); - if (result.affectedRows === 0) { - return callback(new Error('Equipment not found')); - } - callback(null, { equipment_id: equipmentId, ...equipmentData }); - } catch (error) { - callback(error); + if (result.affectedRows === 0) { + throw new Error('Equipment not found'); } + return { equipment_id: equipmentId, ...equipmentData }; }, - // UPDATE MAP POSITION - 지도상 위치 업데이트 (선택적으로 workplace_id도 업데이트) - updateMapPosition: async (equipmentId, positionData, callback) => { - try { - const db = await getDb(); + // UPDATE MAP POSITION - 지도상 위치 업데이트 + updateMapPosition: async (equipmentId, positionData) => { + const db = await getDb(); - // workplace_id가 포함된 경우 함께 업데이트 - const hasWorkplaceId = positionData.workplace_id !== undefined; + const hasWorkplaceId = positionData.workplace_id !== undefined; - const query = hasWorkplaceId ? ` - UPDATE equipments SET - workplace_id = ?, - map_x_percent = ?, - map_y_percent = ?, - map_width_percent = ?, - map_height_percent = ?, - updated_at = NOW() - WHERE equipment_id = ? - ` : ` - UPDATE equipments SET - map_x_percent = ?, - map_y_percent = ?, - map_width_percent = ?, - map_height_percent = ?, - updated_at = NOW() - WHERE equipment_id = ? - `; + const query = hasWorkplaceId ? ` + UPDATE equipments SET + workplace_id = ?, + map_x_percent = ?, + map_y_percent = ?, + map_width_percent = ?, + map_height_percent = ?, + updated_at = NOW() + WHERE equipment_id = ? + ` : ` + UPDATE equipments SET + map_x_percent = ?, + map_y_percent = ?, + map_width_percent = ?, + map_height_percent = ?, + updated_at = NOW() + WHERE equipment_id = ? + `; - const values = hasWorkplaceId ? [ - positionData.workplace_id, - positionData.map_x_percent, - positionData.map_y_percent, - positionData.map_width_percent, - positionData.map_height_percent, - equipmentId - ] : [ - positionData.map_x_percent, - positionData.map_y_percent, - positionData.map_width_percent, - positionData.map_height_percent, - equipmentId - ]; + const values = hasWorkplaceId ? [ + positionData.workplace_id, + positionData.map_x_percent, + positionData.map_y_percent, + positionData.map_width_percent, + positionData.map_height_percent, + equipmentId + ] : [ + positionData.map_x_percent, + positionData.map_y_percent, + positionData.map_width_percent, + positionData.map_height_percent, + equipmentId + ]; - 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); + const [result] = await db.query(query, values); + if (result.affectedRows === 0) { + throw new Error('Equipment not found'); } + return { equipment_id: equipmentId, ...positionData }; }, // DELETE - 설비 삭제 - delete: async (equipmentId, callback) => { - try { - const db = await getDb(); - const query = 'DELETE FROM equipments WHERE equipment_id = ?'; - - const [result] = await db.query(query, [equipmentId]); - if (result.affectedRows === 0) { - return callback(new Error('Equipment not found')); - } - callback(null, { equipment_id: equipmentId }); - } catch (error) { - callback(error); + delete: async (equipmentId) => { + const db = await getDb(); + const [result] = await db.query('DELETE FROM equipments WHERE equipment_id = ?', [equipmentId]); + if (result.affectedRows === 0) { + throw new Error('Equipment not found'); } + return { equipment_id: equipmentId }; }, // CHECK DUPLICATE CODE - 설비 코드 중복 확인 - checkDuplicateCode: async (equipmentCode, excludeEquipmentId, callback) => { - try { - const db = await getDb(); - let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?'; - const values = [equipmentCode]; + checkDuplicateCode: async (equipmentCode, excludeEquipmentId) => { + const db = await getDb(); + let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?'; + const values = [equipmentCode]; - if (excludeEquipmentId) { - query += ' AND equipment_id != ?'; - values.push(excludeEquipmentId); - } - - const [rows] = await db.query(query, values); - callback(null, rows.length > 0); - } catch (error) { - callback(error); + if (excludeEquipmentId) { + query += ' AND equipment_id != ?'; + values.push(excludeEquipmentId); } + + const [rows] = await db.query(query, values); + return rows.length > 0; }, // GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회 - getEquipmentTypes: async (callback) => { - try { - const db = await getDb(); - const 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); - } + getEquipmentTypes: async () => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT DISTINCT equipment_type + FROM equipments + WHERE equipment_type IS NOT NULL + ORDER BY equipment_type ASC` + ); + return rows.map(row => row.equipment_type); }, - // GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 (TKP-001 형식) - getNextEquipmentCode: async (prefix = 'TKP', callback) => { - try { - const db = await getDb(); - // 해당 접두사로 시작하는 가장 큰 번호 찾기 - const query = ` - SELECT equipment_code - FROM equipments - WHERE equipment_code LIKE ? - ORDER BY equipment_code DESC - LIMIT 1 - `; + // GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 + getNextEquipmentCode: async (prefix = 'TKP') => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT equipment_code + FROM equipments + WHERE equipment_code LIKE ? + ORDER BY equipment_code DESC + LIMIT 1`, + [`${prefix}-%`] + ); - const [rows] = await db.query(query, [`${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) { - nextNumber = parseInt(match[1], 10) + 1; - } + let nextNumber = 1; + if (rows.length > 0) { + const lastCode = rows[0].equipment_code; + const match = lastCode.match(new RegExp(`^${prefix}-(\\d+)$`)); + if (match) { + nextNumber = parseInt(match[1], 10) + 1; } - - // 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 { - const db = await getDb(); - const query = ` - INSERT INTO equipment_photos ( - equipment_id, photo_path, description, display_order, uploaded_by - ) VALUES (?, ?, ?, ?, ?) - `; + addPhoto: async (equipmentId, photoData) => { + const db = await getDb(); + const values = [ + equipmentId, + photoData.photo_path, + photoData.description || null, + photoData.display_order || 0, + photoData.uploaded_by || null + ]; - const values = [ - equipmentId, - photoData.photo_path, - photoData.description || null, - photoData.display_order || 0, - photoData.uploaded_by || null - ]; + const [result] = await db.query( + `INSERT INTO equipment_photos ( + equipment_id, photo_path, description, display_order, uploaded_by + ) VALUES (?, ?, ?, ?, ?)`, + values + ); - const [result] = await db.query(query, values); - callback(null, { - photo_id: result.insertId, - equipment_id: equipmentId, - ...photoData - }); - } catch (error) { - callback(error); - } + return { + photo_id: result.insertId, + equipment_id: equipmentId, + ...photoData + }; }, - // GET PHOTOS - 설비 사진 조회 - getPhotos: async (equipmentId, callback) => { - try { - const db = await getDb(); - const 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); - } + getPhotos: async (equipmentId) => { + const db = await getDb(); + 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`, + [equipmentId] + ); + return rows; }, - // DELETE PHOTO - 설비 사진 삭제 - deletePhoto: async (photoId, callback) => { - try { - const db = await getDb(); + deletePhoto: async (photoId) => { + const db = await getDb(); - // 먼저 사진 정보 조회 (파일 삭제용) - const [photo] = await db.query( - 'SELECT photo_path FROM equipment_photos WHERE photo_id = ?', - [photoId] - ); + 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')); - } - - callback(null, { photo_id: photoId, photo_path: photo[0]?.photo_path }); - } catch (error) { - callback(error); + if (result.affectedRows === 0) { + throw new Error('Photo not found'); } + + return { photo_id: photoId, photo_path: photo[0]?.photo_path }; }, // ========================================== // 설비 임시 이동 // ========================================== - // MOVE TEMPORARILY - 설비 임시 이동 - moveTemporarily: async (equipmentId, moveData, callback) => { - try { - const db = await getDb(); + moveTemporarily: async (equipmentId, moveData) => { + const db = await getDb(); - // 1. 설비 현재 위치 업데이트 - const updateQuery = ` - UPDATE equipments SET - current_workplace_id = ?, - current_map_x_percent = ?, - current_map_y_percent = ?, - current_map_width_percent = ?, - current_map_height_percent = ?, - is_temporarily_moved = TRUE, - moved_at = NOW(), - moved_by = ?, - updated_at = NOW() - WHERE equipment_id = ? - `; - - const updateValues = [ + const [result] = await db.query( + `UPDATE equipments SET + current_workplace_id = ?, + current_map_x_percent = ?, + current_map_y_percent = ?, + current_map_width_percent = ?, + current_map_height_percent = ?, + is_temporarily_moved = TRUE, + moved_at = NOW(), + moved_by = ?, + updated_at = NOW() + 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) { + throw new Error('Equipment not found'); + } - if (result.affectedRows === 0) { - return callback(new Error('Equipment not found')); - } - - // 2. 이동 이력 기록 - const logQuery = ` - 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, [ + 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', ?, ?, ?, ?, ?, ?, ?, ?)`, + [ equipmentId, moveData.from_workplace_id || null, moveData.target_workplace_id, @@ -493,137 +404,107 @@ 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 { - const db = await getDb(); + 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] - ); + 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')); - } + if (!equipment[0]) { + throw new Error('Equipment not found'); + } - // 2. 임시 위치 필드 초기화 - const updateQuery = ` - UPDATE equipments SET - current_workplace_id = NULL, - current_map_x_percent = NULL, - current_map_y_percent = NULL, - current_map_width_percent = NULL, - current_map_height_percent = NULL, - is_temporarily_moved = FALSE, - moved_at = NULL, - moved_by = NULL, - updated_at = NOW() - WHERE equipment_id = ? - `; + await db.query( + `UPDATE equipments SET + current_workplace_id = NULL, + current_map_x_percent = NULL, + current_map_y_percent = NULL, + current_map_width_percent = NULL, + current_map_height_percent = NULL, + is_temporarily_moved = FALSE, + moved_at = NULL, + moved_by = NULL, + updated_at = NOW() + WHERE equipment_id = ?`, + [equipmentId] + ); - await db.query(updateQuery, [equipmentId]); - - // 3. 복귀 이력 기록 - const logQuery = ` - 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, [ + 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', ?, ?, ?, '원위치 복귀', ?)`, + [ 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 { - const db = await getDb(); - const query = ` - SELECT - e.*, - w_orig.workplace_name AS original_workplace_name, - w_curr.workplace_name AS current_workplace_name, - u.name AS moved_by_name - FROM equipments e - LEFT JOIN workplaces w_orig ON e.workplace_id = w_orig.workplace_id - LEFT JOIN workplaces w_curr ON e.current_workplace_id = w_curr.workplace_id - LEFT JOIN users u ON e.moved_by = u.user_id - 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); - } + getTemporarilyMoved: async () => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + e.*, + w_orig.workplace_name AS original_workplace_name, + w_curr.workplace_name AS current_workplace_name, + u.name AS moved_by_name + FROM equipments e + LEFT JOIN workplaces w_orig ON e.workplace_id = w_orig.workplace_id + LEFT JOIN workplaces w_curr ON e.current_workplace_id = w_curr.workplace_id + LEFT JOIN users u ON e.moved_by = u.user_id + WHERE e.is_temporarily_moved = TRUE + ORDER BY e.moved_at DESC` + ); + return rows; }, - // GET MOVE LOGS - 설비 이동 이력 조회 - getMoveLogs: async (equipmentId, callback) => { - try { - const db = await getDb(); - const query = ` - SELECT - eml.*, - w_from.workplace_name AS from_workplace_name, - w_to.workplace_name AS to_workplace_name, - u.name AS moved_by_name - FROM equipment_move_logs eml - LEFT JOIN workplaces w_from ON eml.from_workplace_id = w_from.workplace_id - LEFT JOIN workplaces w_to ON eml.to_workplace_id = w_to.workplace_id - LEFT JOIN users u ON eml.moved_by = u.user_id - WHERE eml.equipment_id = ? - ORDER BY eml.moved_at DESC - `; - - const [rows] = await db.query(query, [equipmentId]); - callback(null, rows); - } catch (error) { - callback(error); - } + getMoveLogs: async (equipmentId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + eml.*, + w_from.workplace_name AS from_workplace_name, + w_to.workplace_name AS to_workplace_name, + u.name AS moved_by_name + FROM equipment_move_logs eml + LEFT JOIN workplaces w_from ON eml.from_workplace_id = w_from.workplace_id + LEFT JOIN workplaces w_to ON eml.to_workplace_id = w_to.workplace_id + LEFT JOIN users u ON eml.moved_by = u.user_id + WHERE eml.equipment_id = ? + ORDER BY eml.moved_at DESC`, + [equipmentId] + ); + return rows; }, // ========================================== // 설비 외부 반출/반입 // ========================================== - // EXPORT EQUIPMENT - 설비 외부 반출 - exportEquipment: async (exportData, callback) => { - try { - const db = await getDb(); + exportEquipment: async (exportData) => { + const db = await getDb(); - // 1. 반출 로그 생성 - const logQuery = ` - INSERT INTO equipment_external_logs ( - equipment_id, log_type, export_date, expected_return_date, - destination, reason, notes, exported_by - ) VALUES (?, 'export', ?, ?, ?, ?, ?, ?) - `; - - const logValues = [ + 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', ?, ?, ?, ?, ?, ?)`, + [ exportData.equipment_id, exportData.export_date || new Date().toISOString().slice(0, 10), exportData.expected_return_date || null, @@ -631,178 +512,144 @@ const EquipmentModel = { exportData.reason || null, exportData.notes || null, exportData.exported_by || null - ]; + ] + ); - const [logResult] = await db.query(logQuery, logValues); + 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] + ); - // 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, { - log_id: logResult.insertId, - equipment_id: exportData.equipment_id, - exported: true - }); - } catch (error) { - callback(error); - } + return { + log_id: logResult.insertId, + equipment_id: exportData.equipment_id, + exported: true + }; }, - // RETURN EQUIPMENT - 설비 반입 (외부에서 복귀) - returnEquipment: async (logId, returnData, callback) => { - try { - const db = await getDb(); + 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] - ); + 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')); - } - - const equipmentId = logs[0].equipment_id; - - // 2. 반출 로그 업데이트 - await db.query( - `UPDATE equipment_external_logs SET - actual_return_date = ?, - returned_by = ?, - notes = CONCAT(IFNULL(notes, ''), '\n반입: ', IFNULL(?, '')), - updated_at = NOW() - WHERE log_id = ?`, - [ - returnData.return_date || new Date().toISOString().slice(0, 10), - returnData.returned_by || null, - returnData.notes || '', - logId - ] - ); - - // 3. 설비 상태 복원 - await db.query( - 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', - [returnData.new_status || 'active', equipmentId] - ); - - callback(null, { - log_id: logId, - equipment_id: equipmentId, - returned: true - }); - } catch (error) { - callback(error); + if (!logs[0]) { + throw new Error('Export log not found'); } + + const equipmentId = logs[0].equipment_id; + + await db.query( + `UPDATE equipment_external_logs SET + actual_return_date = ?, + returned_by = ?, + notes = CONCAT(IFNULL(notes, ''), '\n반입: ', IFNULL(?, '')), + updated_at = NOW() + WHERE log_id = ?`, + [ + returnData.return_date || new Date().toISOString().slice(0, 10), + returnData.returned_by || null, + returnData.notes || '', + logId + ] + ); + + await db.query( + 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', + [returnData.new_status || 'active', equipmentId] + ); + + return { + log_id: logId, + equipment_id: equipmentId, + returned: true + }; }, - // GET EXTERNAL LOGS - 설비 외부 반출 이력 조회 - getExternalLogs: async (equipmentId, callback) => { - try { - const db = await getDb(); - const query = ` - SELECT - eel.*, - u_exp.name AS exported_by_name, - u_ret.name AS returned_by_name - FROM equipment_external_logs eel - 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); - } + getExternalLogs: async (equipmentId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + eel.*, + u_exp.name AS exported_by_name, + u_ret.name AS returned_by_name + FROM equipment_external_logs eel + 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`, + [equipmentId] + ); + return rows; }, - // GET EXPORTED EQUIPMENTS - 현재 외부 반출 중인 설비 목록 - getExportedEquipments: async (callback) => { - try { - const db = await getDb(); - const query = ` - SELECT - e.*, - w.workplace_name, - eel.export_date, - eel.expected_return_date, - eel.destination, - eel.reason, - u.name AS exported_by_name - FROM equipments e - INNER JOIN ( - SELECT equipment_id, MAX(log_id) AS latest_log_id - FROM equipment_external_logs - WHERE actual_return_date IS NULL - GROUP BY equipment_id - ) latest ON e.equipment_id = latest.equipment_id - INNER JOIN equipment_external_logs eel ON eel.log_id = latest.latest_log_id - 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); - } + getExportedEquipments: async () => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + e.*, + w.workplace_name, + eel.export_date, + eel.expected_return_date, + eel.destination, + eel.reason, + u.name AS exported_by_name + FROM equipments e + INNER JOIN ( + SELECT equipment_id, MAX(log_id) AS latest_log_id + FROM equipment_external_logs + WHERE actual_return_date IS NULL + GROUP BY equipment_id + ) latest ON e.equipment_id = latest.equipment_id + INNER JOIN equipment_external_logs eel ON eel.log_id = latest.latest_log_id + 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` + ); + return rows; }, // ========================================== - // 설비 수리 신청 (work_issue_reports 연동) + // 설비 수리 신청 // ========================================== - // CREATE REPAIR REQUEST - 수리 신청 (신고 시스템 활용) - createRepairRequest: async (requestData, callback) => { - try { - const db = await getDb(); + 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" + const [categories] = await db.query( + "SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리' LIMIT 1" + ); + + if (!categories[0]) { + throw new Error('설비 수리 카테고리가 없습니다'); + } + + const categoryId = categories[0].category_id; + + let itemId = requestData.item_id; + if (!itemId) { + const [items] = await db.query( + 'SELECT item_id FROM issue_report_items WHERE category_id = ? LIMIT 1', + [categoryId] ); + itemId = items[0]?.item_id; + } - if (!categories[0]) { - return callback(new Error('설비 수리 카테고리가 없습니다')); - } + const photos = requestData.photo_paths || []; - const categoryId = categories[0].category_id; - - // 항목 ID 조회 (지정된 항목이 없으면 첫번째 항목 사용) - let itemId = requestData.item_id; - if (!itemId) { - const [items] = await db.query( - 'SELECT item_id FROM issue_report_items WHERE category_id = ? LIMIT 1', - [categoryId] - ); - itemId = items[0]?.item_id; - } - - // 사진 경로 분리 (최대 5장) - const photos = requestData.photo_paths || []; - - // work_issue_reports에 삽입 - const 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 = [ + 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')`, + [ requestData.reported_by || null, categoryId, itemId, @@ -814,135 +661,103 @@ const EquipmentModel = { photos[2] || null, photos[3] || null, photos[4] || null - ]; + ] + ); - const [result] = await db.query(query, values); + await db.query( + 'UPDATE equipments SET status = ?, updated_at = NOW() WHERE equipment_id = ?', + ['repair_needed', requestData.equipment_id] + ); - // 설비 상태를 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, - equipment_name: requestData.equipment_name || '설비', - repair_type: requestData.repair_type || '일반 수리', - request_id: result.insertId, - created_by: requestData.reported_by - }); - } catch (notifError) { - console.error('알림 생성 실패:', notifError); - // 알림 생성 실패해도 수리 신청은 성공으로 처리 - } - - callback(null, { - report_id: result.insertId, + try { + await notificationModel.createRepairNotification({ equipment_id: requestData.equipment_id, - created: true + equipment_name: requestData.equipment_name || '설비', + repair_type: requestData.repair_type || '일반 수리', + request_id: result.insertId, + created_by: requestData.reported_by }); - } catch (error) { - callback(error); + } catch (notifError) { + // 알림 생성 실패해도 수리 신청은 성공으로 처리 } + + return { + report_id: result.insertId, + equipment_id: requestData.equipment_id, + created: true + }; }, - // GET REPAIR HISTORY - 설비 수리 이력 조회 - getRepairHistory: async (equipmentId, callback) => { - try { - const db = await getDb(); - const query = ` - SELECT - wir.*, - irc.category_name, - iri.item_name, - u_rep.name AS reported_by_name, - u_res.name AS resolved_by_name, - w.workplace_name - FROM work_issue_reports wir - LEFT JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id - LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id - LEFT JOIN users u_rep ON wir.reporter_id = u_rep.user_id - LEFT JOIN users u_res ON wir.resolved_by = u_res.user_id - LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id - WHERE wir.equipment_id = ? - ORDER BY wir.created_at DESC - `; - - const [rows] = await db.query(query, [equipmentId]); - callback(null, rows); - } catch (error) { - callback(error); - } + getRepairHistory: async (equipmentId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + wir.*, + irc.category_name, + iri.item_name, + u_rep.name AS reported_by_name, + u_res.name AS resolved_by_name, + w.workplace_name + FROM work_issue_reports wir + LEFT JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id + LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id + LEFT JOIN users u_rep ON wir.reporter_id = u_rep.user_id + LEFT JOIN users u_res ON wir.resolved_by = u_res.user_id + LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id + WHERE wir.equipment_id = ? + ORDER BY wir.created_at DESC`, + [equipmentId] + ); + return rows; }, - // GET REPAIR CATEGORIES - 설비 수리 항목 목록 조회 - getRepairCategories: async (callback) => { - try { - const db = await getDb(); - - // 설비 수리 카테고리의 항목들 조회 - const 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); - } + getRepairCategories: async () => { + const db = await getDb(); + 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` + ); + return rows; }, - // ADD REPAIR CATEGORY - 새 수리 항목 추가 - addRepairCategory: async (itemName, callback) => { - try { - const db = await getDb(); + addRepairCategory: async (itemName) => { + const db = await getDb(); - // 설비 수리 카테고리 ID 조회 - const [categories] = await db.query( - "SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리'" - ); + const [categories] = await db.query( + "SELECT category_id FROM issue_report_categories WHERE category_name = '설비 수리'" + ); - if (categories.length === 0) { - return callback(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 }); - } - - // 다음 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); + if (categories.length === 0) { + 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) { + return { item_id: existing[0].item_id, item_name: itemName, isNew: false }; + } + + 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] + ); + + return { item_id: result.insertId, item_name: itemName, isNew: true }; } }; diff --git a/system1-factory/api/models/pageAccessModel.js b/system1-factory/api/models/pageAccessModel.js deleted file mode 100644 index f6801f6..0000000 --- a/system1-factory/api/models/pageAccessModel.js +++ /dev/null @@ -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; diff --git a/system1-factory/api/models/tbmModel.js b/system1-factory/api/models/tbmModel.js index b459dfe..6ee7e04 100644 --- a/system1-factory/api/models/tbmModel.js +++ b/system1-factory/api/models/tbmModel.js @@ -4,19 +4,13 @@ const { getDb } = require('../dbPool'); const TbmModel = { // ==================== TBM 세션 관련 ==================== - /** - * TBM 세션 생성 - */ - createSession: async (sessionData, callback) => { - try { - const db = await getDb(); - const sql = ` - INSERT INTO tbm_sessions - (session_date, leader_id, project_id, work_type_id, task_id, work_location, created_by) - VALUES (?, ?, ?, ?, ?, ?, ?) - `; - - const values = [ + createSession: async (sessionData) => { + const db = await getDb(); + const [result] = await db.query( + `INSERT INTO tbm_sessions + (session_date, leader_id, project_id, work_type_id, task_id, work_location, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [ sessionData.session_date, sessionData.leader_id, sessionData.project_id || null, @@ -24,202 +18,139 @@ const TbmModel = { sessionData.task_id || null, sessionData.work_location || null, sessionData.created_by - ]; - - const [result] = await db.query(sql, values); - callback(null, result); - } catch (err) { - callback(err); - } + ] + ); + return result; }, - /** - * 특정 날짜의 TBM 세션 조회 - */ - getSessionsByDate: async (date, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - s.*, - w.worker_name as leader_name, - w.job_type as leader_job_type, - u.username as created_by_username, - u.name as created_by_name, - COUNT(DISTINCT ta.worker_id) as team_member_count, - GROUP_CONCAT(DISTINCT w2.worker_name ORDER BY ta.assignment_id SEPARATOR ', ') as team_member_names, - -- 이동 수 (이 세션이 source 또는 dest인 이동 건수) - (SELECT COUNT(*) FROM tbm_transfers tf - WHERE (tf.source_session_id = s.session_id OR tf.dest_session_id = s.session_id) - AND tf.transfer_date = s.session_date) as transfer_count, - -- 첫 번째 팀원의 작업 정보 가져오기 - first_ta.project_id, - first_ta.work_type_id, - first_ta.task_id, - first_ta.workplace_id, - first_p.project_name, - first_wt.name as work_type_name, - first_t.task_name, - first_wp.workplace_name as work_location - FROM tbm_sessions s - LEFT JOIN workers w ON s.leader_id = w.worker_id - LEFT JOIN sso_users u ON s.created_by = u.user_id - LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id - LEFT JOIN workers w2 ON ta.worker_id = w2.worker_id - -- 첫 번째 팀원 정보 (가장 먼저 등록된 작업) - LEFT JOIN ( - SELECT * FROM tbm_team_assignments - WHERE (session_id, assignment_id) IN ( - SELECT session_id, MIN(assignment_id) - FROM tbm_team_assignments - GROUP BY session_id - ) - ) first_ta ON s.session_id = first_ta.session_id - LEFT JOIN projects first_p ON first_ta.project_id = first_p.project_id - LEFT JOIN work_types first_wt ON first_ta.work_type_id = first_wt.id - LEFT JOIN tasks first_t ON first_ta.task_id = first_t.task_id - LEFT JOIN workplaces first_wp ON first_ta.workplace_id = first_wp.workplace_id - WHERE s.session_date = ? - GROUP BY s.session_id - ORDER BY s.session_id DESC - `; - - const [rows] = await db.query(sql, [date]); - callback(null, rows); - } catch (err) { - callback(err); - } + getSessionsByDate: async (date) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + s.*, + w.worker_name as leader_name, + w.job_type as leader_job_type, + u.username as created_by_username, + u.name as created_by_name, + COUNT(DISTINCT ta.worker_id) as team_member_count, + GROUP_CONCAT(DISTINCT w2.worker_name ORDER BY ta.assignment_id SEPARATOR ', ') as team_member_names, + (SELECT COUNT(*) FROM tbm_transfers tf + WHERE (tf.source_session_id = s.session_id OR tf.dest_session_id = s.session_id) + AND tf.transfer_date = s.session_date) as transfer_count, + first_ta.project_id, + first_ta.work_type_id, + first_ta.task_id, + first_ta.workplace_id, + first_p.project_name, + first_wt.name as work_type_name, + first_t.task_name, + first_wp.workplace_name as work_location + FROM tbm_sessions s + LEFT JOIN workers w ON s.leader_id = w.worker_id + LEFT JOIN sso_users u ON s.created_by = u.user_id + LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id + LEFT JOIN workers w2 ON ta.worker_id = w2.worker_id + LEFT JOIN ( + SELECT * FROM tbm_team_assignments + WHERE (session_id, assignment_id) IN ( + SELECT session_id, MIN(assignment_id) + FROM tbm_team_assignments + GROUP BY session_id + ) + ) first_ta ON s.session_id = first_ta.session_id + LEFT JOIN projects first_p ON first_ta.project_id = first_p.project_id + LEFT JOIN work_types first_wt ON first_ta.work_type_id = first_wt.id + LEFT JOIN tasks first_t ON first_ta.task_id = first_t.task_id + LEFT JOIN workplaces first_wp ON first_ta.workplace_id = first_wp.workplace_id + WHERE s.session_date = ? + GROUP BY s.session_id + ORDER BY s.session_id DESC`, + [date] + ); + return rows; }, - /** - * TBM 세션 상세 조회 - */ - getSessionById: async (sessionId, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - s.*, - w.worker_name as leader_name, - w.job_type as leader_job_type, - w.phone_number as leader_phone, - u.username as created_by_username, - u.name as created_by_name, - COUNT(DISTINCT ta.worker_id) as team_member_count, - first_p.project_name, - first_p.job_no, - first_wt.name as work_type_name, - first_wt.category as work_type_category, - first_t.task_name, - first_t.description as task_description, - first_wp.workplace_name as work_location, - first_wc.category_name as workplace_category_name - FROM tbm_sessions s - LEFT JOIN workers w ON s.leader_id = w.worker_id - LEFT JOIN sso_users u ON s.created_by = u.user_id - LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id - LEFT JOIN ( - SELECT * FROM tbm_team_assignments - WHERE (session_id, assignment_id) IN ( - SELECT session_id, MIN(assignment_id) - FROM tbm_team_assignments - GROUP BY session_id - ) - ) first_ta ON s.session_id = first_ta.session_id - LEFT JOIN projects first_p ON first_ta.project_id = first_p.project_id - LEFT JOIN work_types first_wt ON first_ta.work_type_id = first_wt.id - LEFT JOIN tasks first_t ON first_ta.task_id = first_t.task_id - LEFT JOIN workplaces first_wp ON first_ta.workplace_id = first_wp.workplace_id - LEFT JOIN workplace_categories first_wc ON first_ta.workplace_category_id = first_wc.category_id - WHERE s.session_id = ? - GROUP BY s.session_id - `; - - const [rows] = await db.query(sql, [sessionId]); - callback(null, rows); - } catch (err) { - callback(err); - } + getSessionById: async (sessionId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + s.*, + w.worker_name as leader_name, + w.job_type as leader_job_type, + w.phone_number as leader_phone, + u.username as created_by_username, + u.name as created_by_name, + COUNT(DISTINCT ta.worker_id) as team_member_count, + first_p.project_name, + first_p.job_no, + first_wt.name as work_type_name, + first_wt.category as work_type_category, + first_t.task_name, + first_t.description as task_description, + first_wp.workplace_name as work_location, + first_wc.category_name as workplace_category_name + FROM tbm_sessions s + LEFT JOIN workers w ON s.leader_id = w.worker_id + LEFT JOIN sso_users u ON s.created_by = u.user_id + LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id + LEFT JOIN ( + SELECT * FROM tbm_team_assignments + WHERE (session_id, assignment_id) IN ( + SELECT session_id, MIN(assignment_id) + FROM tbm_team_assignments + GROUP BY session_id + ) + ) first_ta ON s.session_id = first_ta.session_id + LEFT JOIN projects first_p ON first_ta.project_id = first_p.project_id + LEFT JOIN work_types first_wt ON first_ta.work_type_id = first_wt.id + LEFT JOIN tasks first_t ON first_ta.task_id = first_t.task_id + LEFT JOIN workplaces first_wp ON first_ta.workplace_id = first_wp.workplace_id + LEFT JOIN workplace_categories first_wc ON first_ta.workplace_category_id = first_wc.category_id + WHERE s.session_id = ? + GROUP BY s.session_id`, + [sessionId] + ); + return rows; }, - /** - * TBM 세션 수정 - */ - updateSession: async (sessionId, sessionData, callback) => { - try { - const db = await getDb(); - const sql = ` - UPDATE tbm_sessions - SET - project_id = ?, - work_location = ?, - status = ?, - updated_at = NOW() - WHERE session_id = ? - `; - - const values = [ - sessionData.project_id, - sessionData.work_location, - sessionData.status, - sessionId - ]; - - const [result] = await db.query(sql, values); - callback(null, result); - } catch (err) { - callback(err); - } + updateSession: async (sessionId, sessionData) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE tbm_sessions + SET project_id = ?, work_location = ?, status = ?, updated_at = NOW() + WHERE session_id = ?`, + [sessionData.project_id, sessionData.work_location, sessionData.status, sessionId] + ); + return result; }, - /** - * TBM 세션 완료 처리 - */ - completeSession: async (sessionId, endTime, callback) => { - try { - const db = await getDb(); - const sql = ` - UPDATE tbm_sessions - SET - status = 'completed', - end_time = ?, - updated_at = NOW() - WHERE session_id = ? - `; - - const [result] = await db.query(sql, [endTime, sessionId]); - callback(null, result); - } catch (err) { - callback(err); - } + completeSession: async (sessionId, endTime) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE tbm_sessions + SET status = 'completed', end_time = ?, updated_at = NOW() + WHERE session_id = ?`, + [endTime, sessionId] + ); + return result; }, - /** - * TBM 세션 완료 처리 (근태 유형 포함) - * @param {number} sessionId - TBM 세션 ID - * @param {string} endTime - 종료 시간 - * @param {Array} attendanceData - [{worker_id, attendance_type, attendance_hours}] - * @param {number} createdBy - 처리자 user_id - */ - completeSessionWithAttendance: async (sessionId, endTime, attendanceData, createdBy, callback) => { - let conn; + completeSessionWithAttendance: async (sessionId, endTime, attendanceData, createdBy) => { + const db = await getDb(); + const conn = await db.getConnection(); try { - const db = await getDb(); - conn = await db.getConnection(); await conn.beginTransaction(); - // 1. 세션 정보 조회 (날짜 확인용) + // 1. 세션 정보 조회 const [sessionRows] = await conn.query( 'SELECT session_date FROM tbm_sessions WHERE session_id = ?', [sessionId] ); if (sessionRows.length === 0) { - await conn.rollback(); - conn.release(); - return callback(null, { affectedRows: 0 }); + await conn.commit(); + return { affectedRows: 0 }; } const sessionDate = sessionRows[0].session_date; - // sessionDate를 YYYY-MM-DD 형식으로 변환 let reportDate; if (sessionDate instanceof Date) { reportDate = sessionDate.toISOString().split('T')[0]; @@ -239,16 +170,14 @@ const TbmModel = { ); } - // 3. 연차(annual) 작업자 → 작업보고서 자동 생성 (project_id=13, 8h) + // 3. 연차 작업자 → 작업보고서 자동 생성 const annualWorkers = attendanceData.filter(a => a.attendance_type === 'annual'); for (const aw of annualWorkers) { - // 해당 작업자의 assignment_id 조회 const [assignRows] = await conn.query( 'SELECT assignment_id FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', [sessionId, aw.worker_id] ); if (assignRows.length > 0) { - // 이미 보고서가 있는지 확인 const [existingReport] = await conn.query( 'SELECT id FROM daily_work_reports WHERE tbm_assignment_id = ?', [assignRows[0].assignment_id] @@ -273,7 +202,6 @@ const TbmModel = { ); await conn.commit(); - conn.release(); // 5. 연차 작업자 근태 동기화 for (const aw of annualWorkers) { @@ -281,64 +209,49 @@ const TbmModel = { const AttendanceModel = require('./attendanceModel'); await AttendanceModel.syncWithWorkReports(aw.worker_id, reportDate); } catch (syncErr) { - console.error('근태 동기화 오류 (무시됨):', syncErr); + // 근태 동기화 오류 (무시됨) } } - callback(null, { affectedRows: 1 }); + return { affectedRows: 1 }; } catch (err) { - if (conn) { - try { await conn.rollback(); } catch (e) {} - conn.release(); - } - callback(err); + try { await conn.rollback(); } catch (e) {} + throw err; + } finally { + conn.release(); } }, - /** - * TBM 세션 삭제 (draft 상태만 가능) - */ - deleteSession: async (sessionId, callback) => { - try { - const db = await getDb(); - // draft 상태인 세션만 삭제 허용 - const [result] = await db.query( - `DELETE FROM tbm_sessions WHERE session_id = ? AND status = 'draft'`, - [sessionId] - ); - callback(null, result); - } catch (err) { - callback(err); - } + deleteSession: async (sessionId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM tbm_sessions WHERE session_id = ? AND status = 'draft'`, + [sessionId] + ); + return result; }, // ==================== 팀 구성 관련 ==================== - /** - * 팀원 추가 (작업자별 상세 정보 포함) - */ - addTeamMember: async (assignmentData, callback) => { - try { - const db = await getDb(); - const sql = ` - INSERT INTO tbm_team_assignments - (session_id, worker_id, split_seq, assigned_role, work_detail, is_present, absence_reason, - project_id, work_type_id, task_id, workplace_category_id, workplace_id, work_hours) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - assigned_role = VALUES(assigned_role), - work_detail = VALUES(work_detail), - is_present = VALUES(is_present), - absence_reason = VALUES(absence_reason), - project_id = VALUES(project_id), - work_type_id = VALUES(work_type_id), - task_id = VALUES(task_id), - workplace_category_id = VALUES(workplace_category_id), - workplace_id = VALUES(workplace_id), - work_hours = COALESCE(VALUES(work_hours), work_hours) - `; - - const values = [ + addTeamMember: async (assignmentData) => { + const db = await getDb(); + const [result] = await db.query( + `INSERT INTO tbm_team_assignments + (session_id, worker_id, split_seq, assigned_role, work_detail, is_present, absence_reason, + project_id, work_type_id, task_id, workplace_category_id, workplace_id, work_hours) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + assigned_role = VALUES(assigned_role), + work_detail = VALUES(work_detail), + is_present = VALUES(is_present), + absence_reason = VALUES(absence_reason), + project_id = VALUES(project_id), + work_type_id = VALUES(work_type_id), + task_id = VALUES(task_id), + workplace_category_id = VALUES(workplace_category_id), + workplace_id = VALUES(workplace_id), + work_hours = COALESCE(VALUES(work_hours), work_hours)`, + [ assignmentData.session_id, assignmentData.worker_id, assignmentData.split_seq || 0, @@ -352,35 +265,25 @@ const TbmModel = { assignmentData.workplace_category_id || null, assignmentData.workplace_id || null, assignmentData.work_hours !== undefined ? assignmentData.work_hours : null - ]; - - const [result] = await db.query(sql, values); - callback(null, result); - } catch (err) { - callback(err); - } + ] + ); + return result; }, - /** - * 분할 항목 추가 (같은 세션+작업자에 split_seq 자동 증가) - */ - addSplitAssignment: async (assignmentData, callback) => { - try { - const db = await getDb(); - // 현재 최대 split_seq 조회 - const [maxRows] = await db.query( - 'SELECT COALESCE(MAX(split_seq), -1) as max_seq FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [assignmentData.session_id, assignmentData.worker_id] - ); - const nextSeq = (maxRows[0].max_seq || 0) + 1; + addSplitAssignment: async (assignmentData) => { + const db = await getDb(); + const [maxRows] = await db.query( + 'SELECT COALESCE(MAX(split_seq), -1) as max_seq FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', + [assignmentData.session_id, assignmentData.worker_id] + ); + const nextSeq = (maxRows[0].max_seq || 0) + 1; - const sql = ` - INSERT INTO tbm_team_assignments - (session_id, worker_id, split_seq, work_hours, project_id, work_type_id, - task_id, workplace_category_id, workplace_id, is_present) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1) - `; - const [result] = await db.query(sql, [ + const [result] = await db.query( + `INSERT INTO tbm_team_assignments + (session_id, worker_id, split_seq, work_hours, project_id, work_type_id, + task_id, workplace_category_id, workplace_id, is_present) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`, + [ assignmentData.session_id, assignmentData.worker_id, nextSeq, @@ -390,278 +293,181 @@ const TbmModel = { assignmentData.task_id || null, assignmentData.workplace_category_id || null, assignmentData.workplace_id || null - ]); - callback(null, { assignment_id: result.insertId, split_seq: nextSeq }); - } catch (err) { - callback(err); - } + ] + ); + return { assignment_id: result.insertId, split_seq: nextSeq }; }, - /** - * 팀 구성 일괄 추가 (작업자별 상세 정보 포함) - */ - addTeamMembers: async (sessionId, members, callback) => { - try { - if (!members || members.length === 0) { - return callback(null, { affectedRows: 0 }); - } - - const db = await getDb(); - const values = members.map(m => [ - sessionId, - m.worker_id, - m.assigned_role || null, - m.work_detail || null, - m.is_present !== undefined ? m.is_present : true, - m.absence_reason || null, - m.project_id || null, - m.work_type_id || null, - m.task_id || null, - m.workplace_category_id || null, - m.workplace_id || null - ]); - - const sql = ` - INSERT INTO tbm_team_assignments - (session_id, worker_id, assigned_role, work_detail, is_present, absence_reason, - project_id, work_type_id, task_id, workplace_category_id, workplace_id) - VALUES ? - `; - - const [result] = await db.query(sql, [values]); - callback(null, result); - } catch (err) { - callback(err); + addTeamMembers: async (sessionId, members) => { + if (!members || members.length === 0) { + return { affectedRows: 0 }; } + + const db = await getDb(); + const values = members.map(m => [ + sessionId, + m.worker_id, + m.assigned_role || null, + m.work_detail || null, + m.is_present !== undefined ? m.is_present : true, + m.absence_reason || null, + m.project_id || null, + m.work_type_id || null, + m.task_id || null, + m.workplace_category_id || null, + m.workplace_id || null + ]); + + const [result] = await db.query( + `INSERT INTO tbm_team_assignments + (session_id, worker_id, assigned_role, work_detail, is_present, absence_reason, + project_id, work_type_id, task_id, workplace_category_id, workplace_id) + VALUES ?`, + [values] + ); + return result; }, - /** - * TBM 세션의 팀 구성 조회 (작업자별 상세 정보 포함) - */ - getTeamMembers: async (sessionId, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - ta.*, - w.worker_name, - w.job_type, - w.phone_number, - w.department, - p.project_name, - wt.name as work_type_name, - t.task_name, - wc.category_name AS workplace_category_name, - wp.workplace_name - FROM tbm_team_assignments ta - INNER JOIN workers w ON ta.worker_id = w.worker_id - LEFT JOIN projects p ON ta.project_id = p.project_id - LEFT JOIN work_types wt ON ta.work_type_id = wt.id - LEFT JOIN tasks t ON ta.task_id = t.task_id - LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id - LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id - WHERE ta.session_id = ? - ORDER BY ta.assigned_at DESC - `; - - const [rows] = await db.query(sql, [sessionId]); - callback(null, rows); - } catch (err) { - callback(err); - } + getTeamMembers: async (sessionId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + ta.*, + w.worker_name, + w.job_type, + w.phone_number, + w.department, + p.project_name, + wt.name as work_type_name, + t.task_name, + wc.category_name AS workplace_category_name, + wp.workplace_name + FROM tbm_team_assignments ta + INNER JOIN workers w ON ta.worker_id = w.worker_id + LEFT JOIN projects p ON ta.project_id = p.project_id + LEFT JOIN work_types wt ON ta.work_type_id = wt.id + LEFT JOIN tasks t ON ta.task_id = t.task_id + LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id + LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id + WHERE ta.session_id = ? + ORDER BY ta.assigned_at DESC`, + [sessionId] + ); + return rows; }, - /** - * 팀원 제거 - */ - removeTeamMember: async (sessionId, workerId, callback) => { - try { - const db = await getDb(); - const sql = ` - DELETE FROM tbm_team_assignments - WHERE session_id = ? AND worker_id = ? - `; - - const [result] = await db.query(sql, [sessionId, workerId]); - callback(null, result); - } catch (err) { - callback(err); - } + removeTeamMember: async (sessionId, workerId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?`, + [sessionId, workerId] + ); + return result; }, - /** - * 세션의 모든 팀원 삭제 - */ - clearAllTeamMembers: async (sessionId, callback) => { - try { - const db = await getDb(); - const sql = ` - DELETE FROM tbm_team_assignments - WHERE session_id = ? - `; - - const [result] = await db.query(sql, [sessionId]); - callback(null, result); - } catch (err) { - callback(err); - } + clearAllTeamMembers: async (sessionId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM tbm_team_assignments WHERE session_id = ?`, + [sessionId] + ); + return result; }, // ==================== 안전 체크리스트 관련 ==================== - /** - * 모든 안전 체크 항목 조회 - */ - getAllSafetyChecks: async (callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT * - FROM tbm_safety_checks - WHERE is_active = 1 - ORDER BY check_category, display_order - `; - - const [rows] = await db.query(sql); - callback(null, rows); - } catch (err) { - callback(err); - } + getAllSafetyChecks: async () => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT * FROM tbm_safety_checks WHERE is_active = 1 ORDER BY check_category, display_order` + ); + return rows; }, - /** - * 카테고리별 안전 체크 항목 조회 - */ - getSafetyChecksByCategory: async (category, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT * - FROM tbm_safety_checks - WHERE check_category = ? AND is_active = 1 - ORDER BY display_order - `; - - const [rows] = await db.query(sql, [category]); - callback(null, rows); - } catch (err) { - callback(err); - } + getSafetyChecksByCategory: async (category) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT * FROM tbm_safety_checks WHERE check_category = ? AND is_active = 1 ORDER BY display_order`, + [category] + ); + return rows; }, - /** - * TBM 세션의 안전 체크 기록 조회 - */ - getSafetyRecords: async (sessionId, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - sr.*, - sc.check_category, - sc.check_item, - sc.description, - sc.is_required, - u.username as checked_by_username, - u.name as checked_by_name - FROM tbm_safety_records sr - INNER JOIN tbm_safety_checks sc ON sr.check_id = sc.check_id - LEFT JOIN sso_users u ON sr.checked_by = u.user_id - WHERE sr.session_id = ? - ORDER BY sc.check_category, sc.display_order - `; - - const [rows] = await db.query(sql, [sessionId]); - callback(null, rows); - } catch (err) { - callback(err); - } + getSafetyRecords: async (sessionId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + sr.*, + sc.check_category, + sc.check_item, + sc.description, + sc.is_required, + u.username as checked_by_username, + u.name as checked_by_name + FROM tbm_safety_records sr + INNER JOIN tbm_safety_checks sc ON sr.check_id = sc.check_id + LEFT JOIN sso_users u ON sr.checked_by = u.user_id + WHERE sr.session_id = ? + ORDER BY sc.check_category, sc.display_order`, + [sessionId] + ); + return rows; }, - /** - * 안전 체크 기록 저장/업데이트 - */ - saveSafetyRecord: async (recordData, callback) => { - try { - const db = await getDb(); - const sql = ` - INSERT INTO tbm_safety_records - (session_id, check_id, is_checked, notes, checked_by, checked_at) - VALUES (?, ?, ?, ?, ?, NOW()) - ON DUPLICATE KEY UPDATE - is_checked = VALUES(is_checked), - notes = VALUES(notes), - checked_by = VALUES(checked_by), - checked_at = NOW() - `; - - const values = [ - recordData.session_id, - recordData.check_id, - recordData.is_checked, - recordData.notes, - recordData.checked_by - ]; - - const [result] = await db.query(sql, values); - callback(null, result); - } catch (err) { - callback(err); - } + saveSafetyRecord: async (recordData) => { + const db = await getDb(); + const [result] = await db.query( + `INSERT INTO tbm_safety_records + (session_id, check_id, is_checked, notes, checked_by, checked_at) + VALUES (?, ?, ?, ?, ?, NOW()) + ON DUPLICATE KEY UPDATE + is_checked = VALUES(is_checked), + notes = VALUES(notes), + checked_by = VALUES(checked_by), + checked_at = NOW()`, + [recordData.session_id, recordData.check_id, recordData.is_checked, recordData.notes, recordData.checked_by] + ); + return result; }, - /** - * 안전 체크 일괄 저장 - */ - saveSafetyRecords: async (sessionId, records, checkedBy, callback) => { - try { - if (!records || records.length === 0) { - return callback(null, { affectedRows: 0 }); - } - - const db = await getDb(); - const values = records.map(r => [ - sessionId, - r.check_id, - r.is_checked, - r.notes || null, - checkedBy - ]); - - const sql = ` - INSERT INTO tbm_safety_records - (session_id, check_id, is_checked, notes, checked_by, checked_at) - VALUES ? - ON DUPLICATE KEY UPDATE - is_checked = VALUES(is_checked), - notes = VALUES(notes), - checked_by = VALUES(checked_by), - checked_at = NOW() - `; - - const [result] = await db.query(sql, [values]); - callback(null, result); - } catch (err) { - callback(err); + saveSafetyRecords: async (sessionId, records, checkedBy) => { + if (!records || records.length === 0) { + return { affectedRows: 0 }; } + + const db = await getDb(); + const values = records.map(r => [ + sessionId, + r.check_id, + r.is_checked, + r.notes || null, + checkedBy + ]); + + const [result] = await db.query( + `INSERT INTO tbm_safety_records + (session_id, check_id, is_checked, notes, checked_by, checked_at) + VALUES ? + ON DUPLICATE KEY UPDATE + is_checked = VALUES(is_checked), + notes = VALUES(notes), + checked_by = VALUES(checked_by), + checked_at = NOW()`, + [values] + ); + return result; }, // ==================== 작업 인계 관련 ==================== - /** - * 작업 인계 생성 - */ - createHandover: async (handoverData, callback) => { - try { - const db = await getDb(); - const sql = ` - INSERT INTO team_handovers - (session_id, from_leader_id, to_leader_id, handover_date, handover_time, - reason, handover_notes, worker_ids) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `; - - const values = [ + createHandover: async (handoverData) => { + const db = await getDb(); + const [result] = await db.query( + `INSERT INTO team_handovers + (session_id, from_leader_id, to_leader_id, handover_date, handover_time, + reason, handover_notes, worker_ids) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ handoverData.session_id, handoverData.from_leader_id, handoverData.to_leader_id, @@ -670,439 +476,332 @@ const TbmModel = { handoverData.reason, handoverData.handover_notes, JSON.stringify(handoverData.worker_ids || []) - ]; - - const [result] = await db.query(sql, values); - callback(null, result); - } catch (err) { - callback(err); - } + ] + ); + return result; }, - /** - * 작업 인계 확인 - */ - confirmHandover: async (handoverId, confirmedBy, callback) => { - try { - const db = await getDb(); - const sql = ` - UPDATE team_handovers - SET - is_confirmed = 1, - confirmed_at = NOW(), - confirmed_by = ? - WHERE handover_id = ? - `; - - const [result] = await db.query(sql, [confirmedBy, handoverId]); - callback(null, result); - } catch (err) { - callback(err); - } + confirmHandover: async (handoverId, confirmedBy) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE team_handovers + SET is_confirmed = 1, confirmed_at = NOW(), confirmed_by = ? + WHERE handover_id = ?`, + [confirmedBy, handoverId] + ); + return result; }, - /** - * 특정 날짜의 작업 인계 목록 조회 - */ - getHandoversByDate: async (date, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - h.*, - w1.worker_name as from_leader_name, - w2.worker_name as to_leader_name, - u.username as confirmed_by_username, - u.name as confirmed_by_name - FROM team_handovers h - INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id - INNER JOIN workers w2 ON h.to_leader_id = w2.worker_id - LEFT JOIN sso_users u ON h.confirmed_by = u.user_id - WHERE h.handover_date = ? - ORDER BY h.handover_time DESC - `; - - const [rows] = await db.query(sql, [date]); - callback(null, rows); - } catch (err) { - callback(err); - } + getHandoversByDate: async (date) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + h.*, + w1.worker_name as from_leader_name, + w2.worker_name as to_leader_name, + u.username as confirmed_by_username, + u.name as confirmed_by_name + FROM team_handovers h + INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id + INNER JOIN workers w2 ON h.to_leader_id = w2.worker_id + LEFT JOIN sso_users u ON h.confirmed_by = u.user_id + WHERE h.handover_date = ? + ORDER BY h.handover_time DESC`, + [date] + ); + return rows; }, - /** - * 인수자가 받은 미확인 인계 건 조회 - */ - getPendingHandovers: async (toLeaderId, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - h.*, - w1.worker_name as from_leader_name, - w1.phone_number as from_leader_phone, - s.work_location - FROM team_handovers h - INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id - LEFT JOIN tbm_sessions s ON h.session_id = s.session_id - WHERE h.to_leader_id = ? AND h.is_confirmed = 0 - ORDER BY h.handover_date DESC, h.handover_time DESC - `; - - const [rows] = await db.query(sql, [toLeaderId]); - callback(null, rows); - } catch (err) { - callback(err); - } + getPendingHandovers: async (toLeaderId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + h.*, + w1.worker_name as from_leader_name, + w1.phone_number as from_leader_phone, + s.work_location + FROM team_handovers h + INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id + LEFT JOIN tbm_sessions s ON h.session_id = s.session_id + WHERE h.to_leader_id = ? AND h.is_confirmed = 0 + ORDER BY h.handover_date DESC, h.handover_time DESC`, + [toLeaderId] + ); + return rows; }, // ==================== 통계 및 리포트 ==================== - /** - * 특정 기간의 TBM 통계 - */ - getTbmStatistics: async (startDate, endDate, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - DATE(session_date) as date, - COUNT(DISTINCT session_id) as session_count, - COUNT(DISTINCT leader_id) as leader_count, - SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count - FROM tbm_sessions - WHERE session_date BETWEEN ? AND ? - GROUP BY DATE(session_date) - ORDER BY date DESC - `; - - const [rows] = await db.query(sql, [startDate, endDate]); - callback(null, rows); - } catch (err) { - callback(err); - } + getTbmStatistics: async (startDate, endDate) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + DATE(session_date) as date, + COUNT(DISTINCT session_id) as session_count, + COUNT(DISTINCT leader_id) as leader_count, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count + FROM tbm_sessions + WHERE session_date BETWEEN ? AND ? + GROUP BY DATE(session_date) + ORDER BY date DESC`, + [startDate, endDate] + ); + return rows; }, - /** - * 리더별 TBM 진행 현황 - */ - getLeaderStatistics: async (startDate, endDate, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - s.leader_id, - w.worker_name as leader_name, - COUNT(DISTINCT s.session_id) as total_sessions, - SUM(CASE WHEN s.status = 'completed' THEN 1 ELSE 0 END) as completed_sessions, - COUNT(DISTINCT ta.worker_id) as total_team_members - FROM tbm_sessions s - INNER JOIN workers w ON s.leader_id = w.worker_id - LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id - WHERE s.session_date BETWEEN ? AND ? - GROUP BY s.leader_id - ORDER BY total_sessions DESC - `; - - const [rows] = await db.query(sql, [startDate, endDate]); - callback(null, rows); - } catch (err) { - callback(err); - } + getLeaderStatistics: async (startDate, endDate) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + s.leader_id, + w.worker_name as leader_name, + COUNT(DISTINCT s.session_id) as total_sessions, + SUM(CASE WHEN s.status = 'completed' THEN 1 ELSE 0 END) as completed_sessions, + COUNT(DISTINCT ta.worker_id) as total_team_members + FROM tbm_sessions s + INNER JOIN workers w ON s.leader_id = w.worker_id + LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id + WHERE s.session_date BETWEEN ? AND ? + GROUP BY s.leader_id + ORDER BY total_sessions DESC`, + [startDate, endDate] + ); + return rows; }, - /** - * 작업보고서가 작성되지 않은 TBM 세션의 팀 배정 조회 - * @param {number|null} userId - 조회할 사용자 ID (null이면 모든 TBM 조회 - 관리자용) - */ - getIncompleteWorkReports: async (userId, callback) => { - try { - const db = await getDb(); + getIncompleteWorkReports: async (userId) => { + const db = await getDb(); - // WHERE 조건 동적 생성 - // TBM 완료(근태 입력) 후에만 작업보고서 작성 가능 - let whereClause = ` - WHERE dwr.id IS NULL + let whereClause = ` + WHERE dwr.id IS NULL + AND s.status = 'completed' + AND (ta.attendance_type IS NULL OR ta.attendance_type != 'annual') + AND ta.task_id IS NOT NULL + AND ta.workplace_id IS NOT NULL + `; + const params = []; + + if (userId !== null && userId !== undefined) { + whereClause = ` + WHERE s.created_by = ? + AND dwr.id IS NULL AND s.status = 'completed' AND (ta.attendance_type IS NULL OR ta.attendance_type != 'annual') AND ta.task_id IS NOT NULL AND ta.workplace_id IS NOT NULL `; - const params = []; - - // userId가 있으면 created_by 조건 추가 (일반 사용자) - if (userId !== null && userId !== undefined) { - whereClause = ` - WHERE s.created_by = ? - AND dwr.id IS NULL - AND s.status = 'completed' - AND (ta.attendance_type IS NULL OR ta.attendance_type != 'annual') - AND ta.task_id IS NOT NULL - AND ta.workplace_id IS NOT NULL - `; - params.push(userId); - } - - const sql = ` - SELECT - ta.assignment_id, - ta.session_id, - ta.worker_id, - ta.project_id, - ta.work_type_id, - ta.task_id, - ta.workplace_category_id, - ta.workplace_id, - ta.attendance_type, - ta.attendance_hours, - ta.work_hours, - s.session_date, - s.status as session_status, - s.created_by, - w.worker_name, - w.job_type, - p.project_name, - wt.name as work_type_name, - t.task_name, - wp.workplace_name, - wc.category_name, - creator.name as created_by_name - FROM tbm_team_assignments ta - INNER JOIN tbm_sessions s ON ta.session_id = s.session_id - INNER JOIN workers w ON ta.worker_id = w.worker_id - LEFT JOIN users creator ON s.created_by = creator.user_id - LEFT JOIN projects p ON ta.project_id = p.project_id - LEFT JOIN work_types wt ON ta.work_type_id = wt.id - LEFT JOIN tasks t ON ta.task_id = t.task_id - LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id - LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id - LEFT JOIN daily_work_reports dwr ON ta.assignment_id = dwr.tbm_assignment_id - ${whereClause} - ORDER BY s.session_date DESC, ta.assignment_id ASC - `; - - const [rows] = await db.query(sql, params); - callback(null, rows); - } catch (err) { - callback(err); + params.push(userId); } + + const [rows] = await db.query( + `SELECT + ta.assignment_id, + ta.session_id, + ta.worker_id, + ta.project_id, + ta.work_type_id, + ta.task_id, + ta.workplace_category_id, + ta.workplace_id, + ta.attendance_type, + ta.attendance_hours, + ta.work_hours, + s.session_date, + s.status as session_status, + s.created_by, + w.worker_name, + w.job_type, + p.project_name, + wt.name as work_type_name, + t.task_name, + wp.workplace_name, + wc.category_name, + creator.name as created_by_name + FROM tbm_team_assignments ta + INNER JOIN tbm_sessions s ON ta.session_id = s.session_id + INNER JOIN workers w ON ta.worker_id = w.worker_id + LEFT JOIN users creator ON s.created_by = creator.user_id + LEFT JOIN projects p ON ta.project_id = p.project_id + LEFT JOIN work_types wt ON ta.work_type_id = wt.id + LEFT JOIN tasks t ON ta.task_id = t.task_id + LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id + LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id + LEFT JOIN daily_work_reports dwr ON ta.assignment_id = dwr.tbm_assignment_id + ${whereClause} + ORDER BY s.session_date DESC, ta.assignment_id ASC`, + params + ); + return rows; }, // ========== 안전 체크리스트 확장 메서드 ========== - /** - * 유형별 안전 체크 항목 조회 - * @param {string} checkType - 체크 유형 (basic, weather, task) - * @param {Object} options - 추가 옵션 (weatherCondition, taskId) - */ - getSafetyChecksByType: async (checkType, options = {}, callback) => { - try { - const db = await getDb(); - let sql = ` - SELECT sc.*, - wc.condition_name as weather_condition_name, - wc.icon as weather_icon, - t.task_name - FROM tbm_safety_checks sc - LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code - LEFT JOIN tasks t ON sc.task_id = t.task_id - WHERE sc.is_active = 1 AND sc.check_type = ? - `; - const params = [checkType]; + getSafetyChecksByType: async (checkType, options = {}) => { + const db = await getDb(); + let sql = ` + SELECT sc.*, + wc.condition_name as weather_condition_name, + wc.icon as weather_icon, + t.task_name + FROM tbm_safety_checks sc + LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code + LEFT JOIN tasks t ON sc.task_id = t.task_id + WHERE sc.is_active = 1 AND sc.check_type = ? + `; + const params = [checkType]; - if (checkType === 'weather' && options.weatherCondition) { - sql += ' AND sc.weather_condition = ?'; - params.push(options.weatherCondition); - } - - if (checkType === 'task' && options.taskId) { - sql += ' AND sc.task_id = ?'; - params.push(options.taskId); - } - - sql += ' ORDER BY sc.check_category, sc.display_order'; - - const [rows] = await db.query(sql, params); - callback(null, rows); - } catch (err) { - callback(err); + if (checkType === 'weather' && options.weatherCondition) { + sql += ' AND sc.weather_condition = ?'; + params.push(options.weatherCondition); } + + if (checkType === 'task' && options.taskId) { + sql += ' AND sc.task_id = ?'; + params.push(options.taskId); + } + + sql += ' ORDER BY sc.check_category, sc.display_order'; + + const [rows] = await db.query(sql, params); + return rows; }, - /** - * 날씨 조건별 안전 체크 항목 조회 (복수 조건) - * @param {string[]} conditions - 날씨 조건 배열 ['rain', 'wind'] - */ - getSafetyChecksByWeather: async (conditions, callback) => { - try { - const db = await getDb(); + getSafetyChecksByWeather: async (conditions) => { + if (!conditions || conditions.length === 0) { + return []; + } - if (!conditions || conditions.length === 0) { - return callback(null, []); - } + const db = await getDb(); + const placeholders = conditions.map(() => '?').join(','); + const [rows] = await db.query( + `SELECT sc.*, + wc.condition_name as weather_condition_name, + wc.icon as weather_icon + FROM tbm_safety_checks sc + LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code + WHERE sc.is_active = 1 + AND sc.check_type = 'weather' + AND sc.weather_condition IN (${placeholders}) + ORDER BY sc.weather_condition, sc.display_order`, + conditions + ); + return rows; + }, - const placeholders = conditions.map(() => '?').join(','); - const sql = ` - SELECT sc.*, - wc.condition_name as weather_condition_name, - wc.icon as weather_icon + getSafetyChecksByTasks: async (taskIds) => { + if (!taskIds || taskIds.length === 0) { + return []; + } + + const db = await getDb(); + const placeholders = taskIds.map(() => '?').join(','); + const [rows] = await db.query( + `SELECT sc.*, + t.task_name, + wt.name as work_type_name + FROM tbm_safety_checks sc + LEFT JOIN tasks t ON sc.task_id = t.task_id + LEFT JOIN work_types wt ON t.work_type_id = wt.id + WHERE sc.is_active = 1 + AND sc.check_type = 'task' + AND sc.task_id IN (${placeholders}) + ORDER BY sc.task_id, sc.display_order`, + taskIds + ); + return rows; + }, + + getFilteredSafetyChecks: async (sessionId, weatherConditions = []) => { + const db = await getDb(); + + // 1. 세션 작업 ID 목록 + const [assignments] = await db.query( + `SELECT DISTINCT task_id FROM tbm_team_assignments WHERE session_id = ? AND task_id IS NOT NULL`, + [sessionId] + ); + const taskIds = assignments.map(a => a.task_id); + + // 2. 기본 체크항목 + const [basicChecks] = await db.query( + `SELECT sc.*, 'basic' as section_type + FROM tbm_safety_checks sc + WHERE sc.is_active = 1 AND sc.check_type = 'basic' + ORDER BY sc.check_category, sc.display_order` + ); + + // 3. 날씨별 체크항목 + let weatherChecks = []; + if (weatherConditions && weatherConditions.length > 0) { + const wcPlaceholders = weatherConditions.map(() => '?').join(','); + const [rows] = await db.query( + `SELECT sc.*, wc.condition_name as weather_condition_name, wc.icon as weather_icon, + 'weather' as section_type FROM tbm_safety_checks sc LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code WHERE sc.is_active = 1 AND sc.check_type = 'weather' - AND sc.weather_condition IN (${placeholders}) - ORDER BY sc.weather_condition, sc.display_order - `; - - const [rows] = await db.query(sql, conditions); - callback(null, rows); - } catch (err) { - callback(err); + AND sc.weather_condition IN (${wcPlaceholders}) + ORDER BY sc.weather_condition, sc.display_order`, + weatherConditions + ); + weatherChecks = rows; } - }, - /** - * 작업별 안전 체크 항목 조회 (복수 작업) - * @param {number[]} taskIds - 작업 ID 배열 - */ - getSafetyChecksByTasks: async (taskIds, callback) => { - try { - const db = await getDb(); - - if (!taskIds || taskIds.length === 0) { - return callback(null, []); - } - - const placeholders = taskIds.map(() => '?').join(','); - const sql = ` - SELECT sc.*, - t.task_name, - wt.name as work_type_name + // 4. 작업별 체크항목 + let taskChecks = []; + if (taskIds.length > 0) { + const taskPlaceholders = taskIds.map(() => '?').join(','); + const [rows] = await db.query( + `SELECT sc.*, t.task_name, wt.name as work_type_name, + 'task' as section_type FROM tbm_safety_checks sc LEFT JOIN tasks t ON sc.task_id = t.task_id LEFT JOIN work_types wt ON t.work_type_id = wt.id WHERE sc.is_active = 1 AND sc.check_type = 'task' - AND sc.task_id IN (${placeholders}) - ORDER BY sc.task_id, sc.display_order - `; - - const [rows] = await db.query(sql, taskIds); - callback(null, rows); - } catch (err) { - callback(err); + AND sc.task_id IN (${taskPlaceholders}) + ORDER BY sc.task_id, sc.display_order`, + taskIds + ); + taskChecks = rows; } + + // 5. 기존 체크 기록 + const [existingRecords] = await db.query( + `SELECT check_id, is_checked, notes FROM tbm_safety_records WHERE session_id = ?`, + [sessionId] + ); + + const recordMap = {}; + existingRecords.forEach(r => { + recordMap[r.check_id] = { is_checked: r.is_checked, notes: r.notes }; + }); + + // 6. 기록 병합 + const mergeWithRecords = (checks) => { + return checks.map(check => ({ + ...check, + is_checked: recordMap[check.check_id]?.is_checked || false, + notes: recordMap[check.check_id]?.notes || null + })); + }; + + return { + basic: mergeWithRecords(basicChecks), + weather: mergeWithRecords(weatherChecks), + task: mergeWithRecords(taskChecks), + totalCount: basicChecks.length + weatherChecks.length + taskChecks.length, + weatherConditions: weatherConditions + }; }, - /** - * TBM 세션에 맞는 필터링된 안전 체크 항목 조회 - * 기본 + 날씨 + 작업별 체크항목 통합 조회 - * @param {number} sessionId - TBM 세션 ID - * @param {string[]} weatherConditions - 날씨 조건 배열 (optional) - */ - getFilteredSafetyChecks: async (sessionId, weatherConditions = [], callback) => { - try { - const db = await getDb(); - - // 1. 세션 정보에서 작업 ID 목록 조회 - const [assignments] = await db.query(` - SELECT DISTINCT task_id - FROM tbm_team_assignments - WHERE session_id = ? AND task_id IS NOT NULL - `, [sessionId]); - - const taskIds = assignments.map(a => a.task_id); - - // 2. 기본 체크항목 조회 - const [basicChecks] = await db.query(` - SELECT sc.*, 'basic' as section_type - FROM tbm_safety_checks sc - WHERE sc.is_active = 1 AND sc.check_type = 'basic' - ORDER BY sc.check_category, sc.display_order - `); - - // 3. 날씨별 체크항목 조회 - let weatherChecks = []; - if (weatherConditions && weatherConditions.length > 0) { - const wcPlaceholders = weatherConditions.map(() => '?').join(','); - const [rows] = await db.query(` - SELECT sc.*, wc.condition_name as weather_condition_name, wc.icon as weather_icon, - 'weather' as section_type - FROM tbm_safety_checks sc - LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code - WHERE sc.is_active = 1 - AND sc.check_type = 'weather' - AND sc.weather_condition IN (${wcPlaceholders}) - ORDER BY sc.weather_condition, sc.display_order - `, weatherConditions); - weatherChecks = rows; - } - - // 4. 작업별 체크항목 조회 - let taskChecks = []; - if (taskIds.length > 0) { - const taskPlaceholders = taskIds.map(() => '?').join(','); - const [rows] = await db.query(` - SELECT sc.*, t.task_name, wt.name as work_type_name, - 'task' as section_type - FROM tbm_safety_checks sc - LEFT JOIN tasks t ON sc.task_id = t.task_id - LEFT JOIN work_types wt ON t.work_type_id = wt.id - WHERE sc.is_active = 1 - AND sc.check_type = 'task' - AND sc.task_id IN (${taskPlaceholders}) - ORDER BY sc.task_id, sc.display_order - `, taskIds); - taskChecks = rows; - } - - // 5. 기존 체크 기록 조회 - const [existingRecords] = await db.query(` - SELECT check_id, is_checked, notes - FROM tbm_safety_records - WHERE session_id = ? - `, [sessionId]); - - const recordMap = {}; - existingRecords.forEach(r => { - recordMap[r.check_id] = { is_checked: r.is_checked, notes: r.notes }; - }); - - // 6. 기록과 병합 - const mergeWithRecords = (checks) => { - return checks.map(check => ({ - ...check, - is_checked: recordMap[check.check_id]?.is_checked || false, - notes: recordMap[check.check_id]?.notes || null - })); - }; - - const result = { - basic: mergeWithRecords(basicChecks), - weather: mergeWithRecords(weatherChecks), - task: mergeWithRecords(taskChecks), - totalCount: basicChecks.length + weatherChecks.length + taskChecks.length, - weatherConditions: weatherConditions - }; - - callback(null, result); - } catch (err) { - callback(err); - } - }, - - /** - * 안전 체크 항목 생성 (관리자용) - */ - createSafetyCheck: async (checkData, callback) => { - try { - const db = await getDb(); - const sql = ` - INSERT INTO tbm_safety_checks - (check_category, check_type, weather_condition, task_id, check_item, description, is_required, display_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `; - - const values = [ + createSafetyCheck: async (checkData) => { + const db = await getDb(); + const [result] = await db.query( + `INSERT INTO tbm_safety_checks + (check_category, check_type, weather_condition, task_id, check_item, description, is_required, display_order) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ checkData.check_category, checkData.check_type || 'basic', checkData.weather_condition || null, @@ -1111,37 +810,20 @@ const TbmModel = { checkData.description || null, checkData.is_required !== false, checkData.display_order || 0 - ]; - - const [result] = await db.query(sql, values); - callback(null, { insertId: result.insertId }); - } catch (err) { - callback(err); - } + ] + ); + return { insertId: result.insertId }; }, - /** - * 안전 체크 항목 수정 (관리자용) - */ - updateSafetyCheck: async (checkId, checkData, callback) => { - try { - const db = await getDb(); - const sql = ` - UPDATE tbm_safety_checks - SET check_category = ?, - check_type = ?, - weather_condition = ?, - task_id = ?, - check_item = ?, - description = ?, - is_required = ?, - display_order = ?, - is_active = ?, - updated_at = NOW() - WHERE check_id = ? - `; - - const values = [ + updateSafetyCheck: async (checkId, checkData) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE tbm_safety_checks + SET check_category = ?, check_type = ?, weather_condition = ?, task_id = ?, + check_item = ?, description = ?, is_required = ?, display_order = ?, + is_active = ?, updated_at = NOW() + WHERE check_id = ?`, + [ checkData.check_category, checkData.check_type || 'basic', checkData.weather_condition || null, @@ -1152,29 +834,18 @@ const TbmModel = { checkData.display_order || 0, checkData.is_active !== false, checkId - ]; - - const [result] = await db.query(sql, values); - callback(null, { affectedRows: result.affectedRows }); - } catch (err) { - callback(err); - } + ] + ); + return { affectedRows: result.affectedRows }; }, - /** - * 안전 체크 항목 삭제 (비활성화) - */ - deleteSafetyCheck: async (checkId, callback) => { - try { - const db = await getDb(); - // 실제 삭제 대신 비활성화 - const sql = `UPDATE tbm_safety_checks SET is_active = 0 WHERE check_id = ?`; - - const [result] = await db.query(sql, [checkId]); - callback(null, { affectedRows: result.affectedRows }); - } catch (err) { - callback(err); - } + deleteSafetyCheck: async (checkId) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE tbm_safety_checks SET is_active = 0 WHERE check_id = ?`, + [checkId] + ); + return { affectedRows: result.affectedRows }; } }; diff --git a/system1-factory/api/models/tbmTransferModel.js b/system1-factory/api/models/tbmTransferModel.js index 5935443..7f60bf1 100644 --- a/system1-factory/api/models/tbmTransferModel.js +++ b/system1-factory/api/models/tbmTransferModel.js @@ -6,11 +6,10 @@ const TbmTransferModel = { * 작업자 이동 실행 (보내기/빼오기) * 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT */ - createTransfer: async (transferData, callback) => { - let conn; + async createTransfer(transferData) { + const db = await getDb(); + const conn = await db.getConnection(); try { - const db = await getDb(); - conn = await db.getConnection(); 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) {} - conn.release(); - } - callback(err); + try { await conn.rollback(); } catch (e) {} + throw err; + } finally { + conn.release(); } }, /** * 이동 취소 (원복) */ - cancelTransfer: async (transferId, callback) => { - let conn; + async cancelTransfer(transferId) { + const db = await getDb(); + const conn = await db.getConnection(); try { - const db = await getDb(); - conn = await db.getConnection(); 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,115 +168,103 @@ 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) {} - conn.release(); - } - callback(err); + try { await conn.rollback(); } catch (e) {} + throw err; + } finally { + conn.release(); } }, /** * 당일 이동 내역 조회 */ - getTransfersByDate: async (date, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - t.*, - w.worker_name, - w.job_type, - sl.worker_name as source_leader_name, - dl.worker_name as dest_leader_name, - u.name as initiated_by_name - FROM tbm_transfers t - INNER JOIN workers w ON t.worker_id = w.worker_id - LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id - LEFT JOIN workers sl ON ss.leader_id = sl.worker_id - LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id - LEFT JOIN workers dl ON ds.leader_id = dl.worker_id - LEFT JOIN sso_users u ON t.initiated_by = u.user_id - WHERE t.transfer_date = ? - ORDER BY t.created_at DESC - `; - const [rows] = await db.query(sql, [date]); - callback(null, rows); - } catch (err) { - callback(err); - } + async getTransfersByDate(date) { + const db = await getDb(); + const sql = ` + SELECT + t.*, + w.worker_name, + w.job_type, + sl.worker_name as source_leader_name, + dl.worker_name as dest_leader_name, + u.name as initiated_by_name + FROM tbm_transfers t + INNER JOIN workers w ON t.worker_id = w.worker_id + LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id + LEFT JOIN workers sl ON ss.leader_id = sl.worker_id + LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id + LEFT JOIN workers dl ON ds.leader_id = dl.worker_id + LEFT JOIN sso_users u ON t.initiated_by = u.user_id + WHERE t.transfer_date = ? + ORDER BY t.created_at DESC + `; + const [rows] = await db.query(sql, [date]); + return rows; }, /** * 당일 전 작업자 배정 현황 조회 */ - getWorkerAssignmentsByDate: async (date, callback) => { - try { - const db = await getDb(); + async getWorkerAssignmentsByDate(date) { + const db = await getDb(); - // 1. 해당 날짜의 모든 배정 가져오기 - const [assignments] = await db.query(` - SELECT - ta.worker_id, - ta.session_id, - ta.work_hours, - w.worker_name, - w.job_type, - s.leader_id, - lw.worker_name as leader_name, - s.status as session_status - FROM tbm_team_assignments ta - INNER JOIN tbm_sessions s ON ta.session_id = s.session_id - INNER JOIN workers w ON ta.worker_id = w.worker_id - LEFT JOIN workers lw ON s.leader_id = lw.worker_id - WHERE s.session_date = ? - ORDER BY w.worker_name - `, [date]); + // 1. 해당 날짜의 모든 배정 가져오기 + const [assignments] = await db.query(` + SELECT + ta.worker_id, + ta.session_id, + ta.work_hours, + w.worker_name, + w.job_type, + s.leader_id, + lw.worker_name as leader_name, + s.status as session_status + FROM tbm_team_assignments ta + INNER JOIN tbm_sessions s ON ta.session_id = s.session_id + INNER JOIN workers w ON ta.worker_id = w.worker_id + LEFT JOIN workers lw ON s.leader_id = lw.worker_id + WHERE s.session_date = ? + ORDER BY w.worker_name + `, [date]); - // 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함) - const [allWorkers] = await db.query( - "SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name" - ); + // 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함) + const [allWorkers] = await db.query( + "SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name" + ); - // 3. 작업자별 배정 현황 구성 - const workerMap = {}; - allWorkers.forEach(w => { - workerMap[w.worker_id] = { - worker_id: w.worker_id, - worker_name: w.worker_name, - job_type: w.job_type, - sessions: [], - total_hours: 0, - available: true - }; - }); + // 3. 작업자별 배정 현황 구성 + const workerMap = {}; + allWorkers.forEach(w => { + workerMap[w.worker_id] = { + worker_id: w.worker_id, + worker_name: w.worker_name, + job_type: w.job_type, + sessions: [], + total_hours: 0, + available: true + }; + }); - assignments.forEach(a => { - const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours); - if (workerMap[a.worker_id]) { - workerMap[a.worker_id].sessions.push({ - session_id: a.session_id, - leader_name: a.leader_name, - work_hours: hours, - session_status: a.session_status - }); - workerMap[a.worker_id].total_hours += hours; - } - }); + assignments.forEach(a => { + const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours); + if (workerMap[a.worker_id]) { + workerMap[a.worker_id].sessions.push({ + session_id: a.session_id, + leader_name: a.leader_name, + work_hours: hours, + session_status: a.session_status + }); + workerMap[a.worker_id].total_hours += hours; + } + }); - // available 판단 - Object.values(workerMap).forEach(w => { - w.available = w.total_hours < 8; - }); + Object.values(workerMap).forEach(w => { + w.available = w.total_hours < 8; + }); - callback(null, Object.values(workerMap)); - } catch (err) { - callback(err); - } + return Object.values(workerMap); } }; diff --git a/system1-factory/api/models/vacationBalanceModel.js b/system1-factory/api/models/vacationBalanceModel.js index d342417..62096d4 100644 --- a/system1-factory/api/models/vacationBalanceModel.js +++ b/system1-factory/api/models/vacationBalanceModel.js @@ -9,251 +9,190 @@ const vacationBalanceModel = { /** * 특정 작업자의 모든 휴가 잔액 조회 (특정 연도) */ - async getByWorkerAndYear(workerId, year, callback) { - try { - const db = await getDb(); - const query = ` - SELECT - vbd.*, - vt.type_name, - vt.type_code, - vt.priority, - vt.is_special - FROM vacation_balance_details vbd - 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); - } + async getByWorkerAndYear(workerId, year) { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + vbd.*, + vt.type_name, + vt.type_code, + vt.priority, + vt.is_special + FROM vacation_balance_details vbd + 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 + `, [workerId, year]); + return rows; }, /** * 특정 작업자의 특정 휴가 유형 잔액 조회 */ - async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) { - try { - const db = await getDb(); - const query = ` - SELECT - vbd.*, - vt.type_name, - vt.type_code - FROM vacation_balance_details vbd - INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - 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); - } + async getByWorkerTypeYear(workerId, vacationTypeId, year) { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + vbd.*, + vt.type_name, + vt.type_code + FROM vacation_balance_details vbd + INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id + WHERE vbd.worker_id = ? + AND vbd.vacation_type_id = ? + AND vbd.year = ? + `, [workerId, vacationTypeId, year]); + return rows; }, /** * 모든 작업자의 휴가 잔액 조회 (특정 연도) - * - 연간 연차 현황 차트용 */ - async getAllByYear(year, callback) { - try { - const db = await getDb(); - const query = ` - SELECT - vbd.*, - w.worker_name, - w.employment_status, - vt.type_name, - vt.type_code, - vt.priority - FROM vacation_balance_details vbd - 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.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); - } + async getAllByYear(year) { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + vbd.*, + w.worker_name, + w.employment_status, + vt.type_name, + vt.type_code, + vt.priority + FROM vacation_balance_details vbd + 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.year = ? + AND w.employment_status = 'employed' + ORDER BY w.worker_name ASC, vt.priority ASC + `, [year]); + return rows; }, /** * 휴가 잔액 생성 */ - async create(balanceData, callback) { - try { - 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); - } + async create(balanceData) { + const db = await getDb(); + const [result] = await db.query(`INSERT INTO vacation_balance_details SET ?`, balanceData); + return result; }, /** * 휴가 잔액 수정 */ - async update(id, updateData, callback) { - try { - 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); - } + async update(id, updateData) { + const db = await getDb(); + const [result] = await db.query(`UPDATE vacation_balance_details SET ? WHERE id = ?`, [updateData, id]); + return result; }, /** * 휴가 잔액 삭제 */ - async delete(id, callback) { - try { - 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); - } + async delete(id) { + const db = await getDb(); + const [result] = await db.query(`DELETE FROM vacation_balance_details WHERE id = ?`, [id]); + return result; }, /** * 작업자의 휴가 사용 일수 업데이트 (차감) - * - 휴가 신청 승인 시 호출 */ - async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) { - try { - const db = await getDb(); - const 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); - } + async deductDays(workerId, vacationTypeId, year, daysToDeduct) { + const db = await getDb(); + 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 = ? + `, [daysToDeduct, workerId, vacationTypeId, year]); + return result; }, /** * 작업자의 휴가 사용 일수 복구 (취소) - * - 휴가 신청 취소/거부 시 호출 */ - async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) { - try { - const db = await getDb(); - const 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); - } + async restoreDays(workerId, vacationTypeId, year, daysToRestore) { + const db = await getDb(); + 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 = ? + `, [daysToRestore, workerId, vacationTypeId, year]); + return result; }, /** * 특정 작업자의 사용 가능한 휴가 일수 확인 - * - 우선순위가 높은 순서대로 차감 가능 여부 확인 */ - async getAvailableVacationDays(workerId, year, callback) { - try { - const db = await getDb(); - const query = ` - SELECT - vbd.id, - vbd.vacation_type_id, - vt.type_name, - vt.type_code, - vt.priority, - vbd.total_days, - vbd.used_days, - vbd.remaining_days - FROM vacation_balance_details vbd - INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? - 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); - } + async getAvailableVacationDays(workerId, year) { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + vbd.id, + vbd.vacation_type_id, + vt.type_name, + vt.type_code, + vt.priority, + vbd.total_days, + vbd.used_days, + vbd.remaining_days + FROM vacation_balance_details vbd + INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id + WHERE vbd.worker_id = ? + AND vbd.year = ? + AND vbd.remaining_days > 0 + ORDER BY vt.priority ASC + `, [workerId, year]); + return rows; }, /** * 작업자별 휴가 잔액 일괄 생성 (연도별) - * - 매년 초 또는 입사 시 사용 */ - async bulkCreate(balances, callback) { - try { - const db = await getDb(); - - if (!balances || balances.length === 0) { - return callback(new Error('생성할 휴가 잔액 데이터가 없습니다')); - } - - const query = `INSERT INTO vacation_balance_details - (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) - VALUES ?`; - - const values = balances.map(b => [ - b.worker_id, - b.vacation_type_id, - b.year, - b.total_days || 0, - b.used_days || 0, - b.notes || null, - b.created_by - ]); - - const [rows] = await db.query(query, [values]); - callback(null, rows); - } catch (error) { - callback(error); + async bulkCreate(balances) { + if (!balances || balances.length === 0) { + 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 ?`; + + const values = balances.map(b => [ + b.worker_id, + b.vacation_type_id, + b.year, + b.total_days || 0, + b.used_days || 0, + b.notes || null, + b.created_by + ]); + + 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} - 차감 결과 + * 휴가 사용 시 우선순위에 따라 잔액에서 차감 */ 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} - 복구 결과 + * 휴가 취소 시 우선순위 역순으로 복구 */ 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,25 +302,20 @@ const vacationBalanceModel = { /** * 특정 ID로 휴가 잔액 조회 */ - async getById(id, callback) { - try { - const db = await getDb(); - const query = ` - SELECT - vbd.*, - w.worker_name, - vt.type_name, - vt.type_code - FROM vacation_balance_details vbd - 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); - } + async getById(id) { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + vbd.*, + w.worker_name, + vt.type_name, + vt.type_code + FROM vacation_balance_details vbd + 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 = ? + `, [id]); + return rows; } }; diff --git a/system1-factory/api/models/vacationTypeModel.js b/system1-factory/api/models/vacationTypeModel.js index 9ecce63..fc3565c 100644 --- a/system1-factory/api/models/vacationTypeModel.js +++ b/system1-factory/api/models/vacationTypeModel.js @@ -9,123 +9,97 @@ const vacationTypeModel = { /** * 모든 활성 휴가 유형 조회 (우선순위 순서대로) */ - async getAll(callback) { - try { - const db = await getDb(); - const 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); - } + async getAll() { + const db = await getDb(); + const [rows] = await db.query(` + SELECT * + FROM vacation_types + WHERE is_active = 1 + ORDER BY priority ASC, id ASC + `); + return rows; }, /** * 시스템 기본 휴가 유형만 조회 */ - async getSystemTypes(callback) { - try { - const db = await getDb(); - const 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); - } + async getSystemTypes() { + const db = await getDb(); + const [rows] = await db.query(` + SELECT * + FROM vacation_types + WHERE is_system = 1 AND is_active = 1 + ORDER BY priority ASC + `); + 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 { - 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); - } + async getById(id) { + const db = await getDb(); + const [rows] = await db.query(`SELECT * FROM vacation_types WHERE id = ?`, [id]); + return rows; }, /** * 휴가 유형 코드로 조회 */ - async getByCode(code, callback) { - try { - 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); - } + async getByCode(code) { + const db = await getDb(); + const [rows] = await db.query(`SELECT * FROM vacation_types WHERE type_code = ?`, [code]); + return rows; }, /** * 휴가 유형 생성 */ - async create(typeData, callback) { - try { - 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); - } + async create(typeData) { + const db = await getDb(); + const [result] = await db.query(`INSERT INTO vacation_types SET ?`, typeData); + return result; }, /** * 휴가 유형 수정 */ - async update(id, updateData, callback) { - try { - 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); - } + async update(id, updateData) { + const db = await getDb(); + const [result] = await db.query(`UPDATE vacation_types SET ? WHERE id = ?`, [updateData, id]); + return result; }, /** * 휴가 유형 삭제 (논리적 삭제 - is_active = 0) */ - async delete(id, callback) { - try { - 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); - } + async delete(id) { + const db = await getDb(); + 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 { - 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); - } + async updatePriority(id, priority) { + const db = await getDb(); + const [result] = await db.query(`UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`, [priority, id]); + return result; } }; diff --git a/system1-factory/api/models/visitRequestModel.js b/system1-factory/api/models/visitRequestModel.js index 87bb6c9..ba71e85 100644 --- a/system1-factory/api/models/visitRequestModel.js +++ b/system1-factory/api/models/visitRequestModel.js @@ -2,484 +2,324 @@ const { getDb } = require('../dbPool'); // ==================== 출입 신청 관리 ==================== -/** - * 출입 신청 생성 - */ -const createVisitRequest = async (requestData, callback) => { - try { - const db = await getDb(); - const { - requester_id, - visitor_company, - visitor_count = 1, - category_id, - workplace_id, - visit_date, - visit_time, - purpose_id, - notes = null - } = requestData; +const createVisitRequest = async (requestData) => { + const db = await getDb(); + const { + requester_id, + visitor_company, + visitor_count = 1, + category_id, + workplace_id, + visit_date, + visit_time, + purpose_id, + notes = null + } = requestData; - const [result] = await db.query( - `INSERT INTO workplace_visit_requests - (requester_id, visitor_company, visitor_count, category_id, workplace_id, - visit_date, visit_time, purpose_id, notes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [requester_id, visitor_company, visitor_count, category_id, workplace_id, - visit_date, visit_time, purpose_id, notes] - ); + const [result] = await db.query( + `INSERT INTO workplace_visit_requests + (requester_id, visitor_company, visitor_count, category_id, workplace_id, + visit_date, visit_time, purpose_id, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [requester_id, visitor_company, visitor_count, category_id, workplace_id, + 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 db = await getDb(); - let query = ` - SELECT - vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, - vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, - vr.purpose_id, vr.notes, vr.status, - vr.approved_by, vr.approved_at, vr.rejection_reason, - vr.created_at, vr.updated_at, - u.username as requester_name, u.name as requester_full_name, - wc.category_name, w.workplace_name, - vpt.purpose_name, - approver.username as approver_name - FROM workplace_visit_requests vr - INNER JOIN users u ON vr.requester_id = u.user_id - INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id - INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id - INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id - LEFT JOIN users approver ON vr.approved_by = approver.user_id - WHERE 1=1 - `; +const getAllVisitRequests = async (filters = {}) => { + const db = await getDb(); + let query = ` + SELECT + vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, + vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, + vr.purpose_id, vr.notes, vr.status, + vr.approved_by, vr.approved_at, vr.rejection_reason, + vr.created_at, vr.updated_at, + u.username as requester_name, u.name as requester_full_name, + wc.category_name, w.workplace_name, + vpt.purpose_name, + approver.username as approver_name + FROM workplace_visit_requests vr + INNER JOIN users u ON vr.requester_id = u.user_id + INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id + INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id + INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id + LEFT JOIN users approver ON vr.approved_by = approver.user_id + WHERE 1=1 + `; - const params = []; + 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); - } - - 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); + 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); + } + + query += ` ORDER BY vr.visit_date DESC, vr.visit_time DESC, vr.created_at DESC`; + + const [rows] = await db.query(query, params); + return rows; }; -/** - * 출입 신청 상세 조회 - */ -const getVisitRequestById = async (requestId, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT - vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, - vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, - vr.purpose_id, vr.notes, vr.status, - vr.approved_by, vr.approved_at, vr.rejection_reason, - vr.created_at, vr.updated_at, - u.username as requester_name, u.name as requester_full_name, - wc.category_name, w.workplace_name, - vpt.purpose_name, - approver.username as approver_name - FROM workplace_visit_requests vr - INNER JOIN users u ON vr.requester_id = u.user_id - INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id - INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id - INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id - LEFT JOIN users approver ON vr.approved_by = approver.user_id - WHERE vr.request_id = ?`, - [requestId] - ); - - callback(null, rows[0]); - } catch (err) { - callback(err); - } +const getVisitRequestById = async (requestId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, + vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, + vr.purpose_id, vr.notes, vr.status, + vr.approved_by, vr.approved_at, vr.rejection_reason, + vr.created_at, vr.updated_at, + u.username as requester_name, u.name as requester_full_name, + wc.category_name, w.workplace_name, + vpt.purpose_name, + approver.username as approver_name + FROM workplace_visit_requests vr + INNER JOIN users u ON vr.requester_id = u.user_id + INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id + INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id + INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id + LEFT JOIN users approver ON vr.approved_by = approver.user_id + WHERE vr.request_id = ?`, + [requestId] + ); + return rows[0]; }; -/** - * 출입 신청 수정 - */ -const updateVisitRequest = async (requestId, requestData, callback) => { - try { - const db = await getDb(); - const { - visitor_company, - visitor_count, - category_id, - workplace_id, - visit_date, - visit_time, - purpose_id, - notes - } = requestData; +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 + } = requestData; - const [result] = await db.query( - `UPDATE workplace_visit_requests - SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?, - visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW() - WHERE request_id = ?`, - [visitor_company, visitor_count, category_id, workplace_id, - visit_date, visit_time, purpose_id, notes, requestId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `UPDATE workplace_visit_requests + SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?, + visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW() + WHERE request_id = ?`, + [visitor_company, visitor_count, category_id, workplace_id, + visit_date, visit_time, purpose_id, notes, requestId] + ); + return result; }; -/** - * 출입 신청 삭제 - */ -const deleteVisitRequest = async (requestId, callback) => { - try { - 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); - } +const deleteVisitRequest = async (requestId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplace_visit_requests WHERE request_id = ?`, + [requestId] + ); + return result; }; -/** - * 출입 신청 승인 - */ -const approveVisitRequest = async (requestId, approvedBy, callback) => { - try { - const db = await getDb(); - const [result] = await db.query( - `UPDATE workplace_visit_requests - SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW() - WHERE request_id = ?`, - [approvedBy, requestId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } +const approveVisitRequest = async (requestId, approvedBy) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE workplace_visit_requests + SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW() + WHERE request_id = ?`, + [approvedBy, requestId] + ); + return result; }; -/** - * 출입 신청 반려 - */ -const rejectVisitRequest = async (requestId, rejectionData, callback) => { - try { - const db = await getDb(); - const { approved_by, rejection_reason } = rejectionData; +const rejectVisitRequest = async (requestId, rejectionData) => { + const db = await getDb(); + const { approved_by, rejection_reason } = rejectionData; - const [result] = await db.query( - `UPDATE workplace_visit_requests - SET status = 'rejected', approved_by = ?, approved_at = NOW(), - rejection_reason = ?, updated_at = NOW() - WHERE request_id = ?`, - [approved_by, rejection_reason, requestId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `UPDATE workplace_visit_requests + SET status = 'rejected', approved_by = ?, approved_at = NOW(), + rejection_reason = ?, updated_at = NOW() + WHERE request_id = ?`, + [approved_by, rejection_reason, requestId] + ); + return result; }; -/** - * 출입 신청 상태 변경 - */ -const updateVisitRequestStatus = async (requestId, status, callback) => { - try { - const db = await getDb(); - const [result] = await db.query( - `UPDATE workplace_visit_requests - SET status = ?, updated_at = NOW() - WHERE request_id = ?`, - [status, requestId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } +const updateVisitRequestStatus = async (requestId, status) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE workplace_visit_requests + SET status = ?, updated_at = NOW() + WHERE request_id = ?`, + [status, requestId] + ); + return result; }; // ==================== 방문 목적 관리 ==================== -/** - * 모든 방문 목적 조회 - */ -const getAllVisitPurposes = async (callback) => { - try { - 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); - } +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` + ); + return rows; }; -/** - * 활성 방문 목적만 조회 - */ -const getActiveVisitPurposes = async (callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT purpose_id, purpose_name, display_order, is_active, created_at - FROM visit_purpose_types - WHERE is_active = TRUE - ORDER BY display_order ASC, purpose_id ASC` - ); - callback(null, rows); - } catch (err) { - callback(err); - } +const getActiveVisitPurposes = 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 + WHERE is_active = TRUE + ORDER BY display_order ASC, purpose_id ASC` + ); + return rows; }; -/** - * 방문 목적 추가 - */ -const createVisitPurpose = async (purposeData, callback) => { - try { - const db = await getDb(); - const { purpose_name, display_order = 0, is_active = true } = purposeData; +const createVisitPurpose = async (purposeData) => { + const db = await getDb(); + const { purpose_name, display_order = 0, is_active = true } = purposeData; - const [result] = await db.query( - `INSERT INTO visit_purpose_types (purpose_name, display_order, is_active) - VALUES (?, ?, ?)`, - [purpose_name, display_order, is_active] - ); - - callback(null, result.insertId); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `INSERT INTO visit_purpose_types (purpose_name, display_order, is_active) + VALUES (?, ?, ?)`, + [purpose_name, display_order, is_active] + ); + return result.insertId; }; -/** - * 방문 목적 수정 - */ -const updateVisitPurpose = async (purposeId, purposeData, callback) => { - try { - const db = await getDb(); - const { purpose_name, display_order, is_active } = purposeData; +const updateVisitPurpose = async (purposeId, purposeData) => { + const db = await getDb(); + const { purpose_name, display_order, is_active } = purposeData; - const [result] = await db.query( - `UPDATE visit_purpose_types - SET purpose_name = ?, display_order = ?, is_active = ? - WHERE purpose_id = ?`, - [purpose_name, display_order, is_active, purposeId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `UPDATE visit_purpose_types + SET purpose_name = ?, display_order = ?, is_active = ? + WHERE purpose_id = ?`, + [purpose_name, display_order, is_active, purposeId] + ); + return result; }; -/** - * 방문 목적 삭제 - */ -const deleteVisitPurpose = async (purposeId, callback) => { - try { - 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); - } +const deleteVisitPurpose = async (purposeId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM visit_purpose_types WHERE purpose_id = ?`, + [purposeId] + ); + return result; }; // ==================== 안전교육 기록 관리 ==================== -/** - * 안전교육 기록 생성 - */ -const createTrainingRecord = async (trainingData, callback) => { - try { - const db = await getDb(); - const { - request_id, - trainer_id, - training_date, - training_start_time, - training_end_time = null, - training_topics = null - } = trainingData; +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 + } = trainingData; - const [result] = await db.query( - `INSERT INTO safety_training_records - (request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics) - VALUES (?, ?, ?, ?, ?, ?)`, - [request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics] - ); - - callback(null, result.insertId); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `INSERT INTO safety_training_records + (request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics) + VALUES (?, ?, ?, ?, ?, ?)`, + [request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics] + ); + return result.insertId; }; -/** - * 특정 출입 신청의 안전교육 기록 조회 - */ -const getTrainingRecordByRequestId = async (requestId, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT - str.training_id, str.request_id, str.trainer_id, str.training_date, - str.training_start_time, str.training_end_time, str.training_topics, - str.signature_data, str.completed_at, str.created_at, str.updated_at, - u.username as trainer_name, u.name as trainer_full_name - FROM safety_training_records str - INNER JOIN users u ON str.trainer_id = u.user_id - WHERE str.request_id = ?`, - [requestId] - ); - - callback(null, rows[0]); - } catch (err) { - callback(err); - } +const getTrainingRecordByRequestId = async (requestId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + str.training_id, str.request_id, str.trainer_id, str.training_date, + str.training_start_time, str.training_end_time, str.training_topics, + str.signature_data, str.completed_at, str.created_at, str.updated_at, + u.username as trainer_name, u.name as trainer_full_name + FROM safety_training_records str + INNER JOIN users u ON str.trainer_id = u.user_id + WHERE str.request_id = ?`, + [requestId] + ); + return rows[0]; }; -/** - * 안전교육 기록 수정 - */ -const updateTrainingRecord = async (trainingId, trainingData, callback) => { - try { - const db = await getDb(); - const { - training_date, - training_start_time, - training_end_time, - training_topics - } = trainingData; +const updateTrainingRecord = async (trainingId, trainingData) => { + const db = await getDb(); + const { training_date, training_start_time, training_end_time, training_topics } = trainingData; - const [result] = await db.query( - `UPDATE safety_training_records - SET training_date = ?, training_start_time = ?, training_end_time = ?, - training_topics = ?, updated_at = NOW() - WHERE training_id = ?`, - [training_date, training_start_time, training_end_time, training_topics, trainingId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } + const [result] = await db.query( + `UPDATE safety_training_records + SET training_date = ?, training_start_time = ?, training_end_time = ?, + training_topics = ?, updated_at = NOW() + WHERE training_id = ?`, + [training_date, training_start_time, training_end_time, training_topics, trainingId] + ); + return result; }; -/** - * 안전교육 완료 (서명 포함) - */ -const completeTraining = async (trainingId, signatureData, callback) => { - try { - const db = await getDb(); - const [result] = await db.query( - `UPDATE safety_training_records - SET signature_data = ?, completed_at = NOW(), updated_at = NOW() - WHERE training_id = ?`, - [signatureData, trainingId] - ); - - callback(null, result); - } catch (err) { - callback(err); - } +const completeTraining = async (trainingId, signatureData) => { + const db = await getDb(); + const [result] = await db.query( + `UPDATE safety_training_records + SET signature_data = ?, completed_at = NOW(), updated_at = NOW() + WHERE training_id = ?`, + [signatureData, trainingId] + ); + return result; }; -/** - * 안전교육 목록 조회 (날짜별 필터) - */ -const getTrainingRecords = async (filters = {}, callback) => { - try { - const db = await getDb(); - let query = ` - SELECT - str.training_id, str.request_id, str.trainer_id, str.training_date, - str.training_start_time, str.training_end_time, str.training_topics, - str.completed_at, str.created_at, str.updated_at, - u.username as trainer_name, u.name as trainer_full_name, - vr.visitor_company, vr.visitor_count, vr.visit_date - FROM safety_training_records str - INNER JOIN users u ON str.trainer_id = u.user_id - INNER JOIN workplace_visit_requests vr ON str.request_id = vr.request_id - WHERE 1=1 - `; +const getTrainingRecords = async (filters = {}) => { + const db = await getDb(); + let query = ` + SELECT + str.training_id, str.request_id, str.trainer_id, str.training_date, + str.training_start_time, str.training_end_time, str.training_topics, + str.completed_at, str.created_at, str.updated_at, + u.username as trainer_name, u.name as trainer_full_name, + vr.visitor_company, vr.visitor_count, vr.visit_date + FROM safety_training_records str + INNER JOIN users u ON str.trainer_id = u.user_id + INNER JOIN workplace_visit_requests vr ON str.request_id = vr.request_id + WHERE 1=1 + `; - const params = []; + const params = []; - if (filters.training_date) { - 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); - } - - 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); + if (filters.training_date) { + 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); + } + + query += ` ORDER BY str.training_date DESC, str.training_start_time DESC`; + + const [rows] = await db.query(query, params); + return rows; }; module.exports = { - // 출입 신청 createVisitRequest, getAllVisitRequests, getVisitRequestById, @@ -488,15 +328,11 @@ module.exports = { approveVisitRequest, rejectVisitRequest, updateVisitRequestStatus, - - // 방문 목적 getAllVisitPurposes, getActiveVisitPurposes, createVisitPurpose, updateVisitPurpose, deleteVisitPurpose, - - // 안전교육 createTrainingRecord, getTrainingRecordByRequestId, updateTrainingRecord, diff --git a/system1-factory/api/models/workIssueModel.js b/system1-factory/api/models/workIssueModel.js index 34ec5ef..c0fb018 100644 --- a/system1-factory/api/models/workIssueModel.js +++ b/system1-factory/api/models/workIssueModel.js @@ -7,195 +7,125 @@ const { getDb } = require('../dbPool'); // ==================== 신고 카테고리 관리 ==================== -/** - * 모든 신고 카테고리 조회 - */ -const getAllCategories = async (callback) => { - try { - 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); - } +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` + ); + return rows; }; -/** - * 타입별 활성 카테고리 조회 (nonconformity/safety) - */ -const getCategoriesByType = async (categoryType, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT category_id, category_type, category_name, description, display_order - FROM issue_report_categories - WHERE category_type = ? AND is_active = TRUE - ORDER BY display_order, category_id`, - [categoryType] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +const getCategoriesByType = async (categoryType) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT category_id, category_type, category_name, description, display_order + FROM issue_report_categories + WHERE category_type = ? AND is_active = TRUE + ORDER BY display_order, category_id`, + [categoryType] + ); + return rows; }; -/** - * 카테고리 생성 - */ -const createCategory = async (categoryData, callback) => { - try { - const db = await getDb(); - const { category_type, category_name, description = null, display_order = 0 } = categoryData; +const createCategory = async (categoryData) => { + const db = await getDb(); + const { category_type, category_name, description = null, display_order = 0 } = categoryData; - const [result] = await db.query( - `INSERT INTO issue_report_categories (category_type, category_name, description, display_order) - VALUES (?, ?, ?, ?)`, - [category_type, category_name, description, display_order] - ); + const [result] = await db.query( + `INSERT INTO issue_report_categories (category_type, category_name, description, display_order) + VALUES (?, ?, ?, ?)`, + [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 db = await getDb(); - const { category_name, description, display_order, is_active } = categoryData; +const updateCategory = async (categoryId, categoryData) => { + const db = await getDb(); + const { category_name, description, display_order, is_active } = categoryData; - const [result] = await db.query( - `UPDATE issue_report_categories - SET category_name = ?, description = ?, display_order = ?, is_active = ? - WHERE category_id = ?`, - [category_name, description, display_order, is_active, categoryId] - ); + const [result] = await db.query( + `UPDATE issue_report_categories + SET category_name = ?, description = ?, display_order = ?, is_active = ? + WHERE category_id = ?`, + [category_name, description, display_order, is_active, categoryId] + ); - callback(null, result); - } catch (err) { - callback(err); - } + return result; }; -/** - * 카테고리 삭제 - */ -const deleteCategory = async (categoryId, callback) => { - try { - 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); - } +const deleteCategory = async (categoryId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM issue_report_categories WHERE category_id = ?`, + [categoryId] + ); + return result; }; // ==================== 사전 정의 신고 항목 관리 ==================== -/** - * 카테고리별 활성 항목 조회 - */ -const getItemsByCategory = async (categoryId, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT item_id, category_id, item_name, description, severity, display_order - FROM issue_report_items - WHERE category_id = ? AND is_active = TRUE - ORDER BY display_order, item_id`, - [categoryId] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +const getItemsByCategory = async (categoryId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT item_id, category_id, item_name, description, severity, display_order + FROM issue_report_items + WHERE category_id = ? AND is_active = TRUE + ORDER BY display_order, item_id`, + [categoryId] + ); + return rows; }; -/** - * 모든 항목 조회 (관리용) - */ -const getAllItems = async (callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT iri.item_id, iri.category_id, iri.item_name, iri.description, - iri.severity, iri.display_order, iri.is_active, iri.created_at, - irc.category_name, irc.category_type - FROM issue_report_items iri - 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); - } +const getAllItems = async () => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT iri.item_id, iri.category_id, iri.item_name, iri.description, + iri.severity, iri.display_order, iri.is_active, iri.created_at, + irc.category_name, irc.category_type + FROM issue_report_items iri + 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` + ); + return rows; }; -/** - * 항목 생성 - */ -const createItem = async (itemData, callback) => { - try { - const db = await getDb(); - const { category_id, item_name, description = null, severity = 'medium', display_order = 0 } = itemData; +const createItem = async (itemData) => { + const db = await getDb(); + const { category_id, item_name, description = null, severity = 'medium', display_order = 0 } = itemData; - const [result] = await db.query( - `INSERT INTO issue_report_items (category_id, item_name, description, severity, display_order) - VALUES (?, ?, ?, ?, ?)`, - [category_id, item_name, description, severity, display_order] - ); + const [result] = await db.query( + `INSERT INTO issue_report_items (category_id, item_name, description, severity, display_order) + VALUES (?, ?, ?, ?, ?)`, + [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 db = await getDb(); - const { item_name, description, severity, display_order, is_active } = itemData; +const updateItem = async (itemId, itemData) => { + const db = await getDb(); + const { item_name, description, severity, display_order, is_active } = itemData; - const [result] = await db.query( - `UPDATE issue_report_items - SET item_name = ?, description = ?, severity = ?, display_order = ?, is_active = ? - WHERE item_id = ?`, - [item_name, description, severity, display_order, is_active, itemId] - ); + const [result] = await db.query( + `UPDATE issue_report_items + SET item_name = ?, description = ?, severity = ?, display_order = ?, is_active = ? + WHERE item_id = ?`, + [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 db = await getDb(); - const [result] = await db.query( - `DELETE FROM issue_report_items WHERE item_id = ?`, - [itemId] - ); - callback(null, result); - } catch (err) { - callback(err); - } +const deleteItem = async (itemId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM issue_report_items WHERE item_id = ?`, + [itemId] + ); + return result; }; // ==================== 문제 신고 관리 ==================== @@ -203,651 +133,534 @@ const deleteItem = async (itemId, callback) => { // 한국 시간 유틸리티 import const { getKoreaDatetime } = require('../utils/dateUtils'); -/** - * 신고 생성 - */ -const createReport = async (reportData, callback) => { - try { - const db = await getDb(); - const { - reporter_id, - factory_category_id = null, - workplace_id = null, - custom_location = null, - tbm_session_id = null, - visit_request_id = null, - issue_category_id, - issue_item_id = null, - additional_description = null, - photo_path1 = null, - photo_path2 = null, - photo_path3 = null, - photo_path4 = null, - photo_path5 = null - } = reportData; +const createReport = async (reportData) => { + const db = await getDb(); + const { + reporter_id, + factory_category_id = null, + workplace_id = null, + custom_location = null, + tbm_session_id = null, + visit_request_id = null, + issue_category_id, + issue_item_id = null, + additional_description = null, + photo_path1 = null, + photo_path2 = null, + photo_path3 = null, + photo_path4 = null, + photo_path5 = null + } = reportData; - // 한국 시간 기준으로 신고 일시 설정 - const reportDate = getKoreaDatetime(); + const reportDate = getKoreaDatetime(); - const [result] = await db.query( - `INSERT INTO work_issue_reports - (reporter_id, report_date, factory_category_id, workplace_id, custom_location, - tbm_session_id, visit_request_id, issue_category_id, issue_item_id, - additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [reporter_id, reportDate, factory_category_id, workplace_id, custom_location, - tbm_session_id, visit_request_id, issue_category_id, issue_item_id, - additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5] - ); + const [result] = await db.query( + `INSERT INTO work_issue_reports + (reporter_id, report_date, factory_category_id, workplace_id, custom_location, + tbm_session_id, visit_request_id, issue_category_id, issue_item_id, + additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [reporter_id, reportDate, factory_category_id, workplace_id, custom_location, + tbm_session_id, visit_request_id, issue_category_id, issue_item_id, + 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] - ); + 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 db = await getDb(); - let query = ` - SELECT - wir.report_id, wir.reporter_id, wir.report_date, - wir.factory_category_id, wir.workplace_id, wir.custom_location, - wir.tbm_session_id, wir.visit_request_id, - wir.issue_category_id, wir.issue_item_id, wir.additional_description, - wir.photo_path1, wir.photo_path2, wir.photo_path3, wir.photo_path4, wir.photo_path5, - wir.status, wir.assigned_department, wir.assigned_user_id, wir.assigned_at, - wir.resolution_notes, wir.resolved_at, - wir.created_at, wir.updated_at, - u.username as reporter_name, u.name as reporter_full_name, - wc.category_name as factory_name, - w.workplace_name, - irc.category_type, irc.category_name as issue_category_name, - iri.item_name as issue_item_name, iri.severity, - assignee.username as assigned_user_name, assignee.name as assigned_full_name - FROM work_issue_reports wir - INNER JOIN users u ON wir.reporter_id = u.user_id - LEFT JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id - LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id - INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id - LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id - LEFT JOIN users assignee ON wir.assigned_user_id = assignee.user_id - WHERE 1=1 - `; +const getAllReports = async (filters = {}) => { + const db = await getDb(); + let query = ` + SELECT + wir.report_id, wir.reporter_id, wir.report_date, + wir.factory_category_id, wir.workplace_id, wir.custom_location, + wir.tbm_session_id, wir.visit_request_id, + wir.issue_category_id, wir.issue_item_id, wir.additional_description, + wir.photo_path1, wir.photo_path2, wir.photo_path3, wir.photo_path4, wir.photo_path5, + wir.status, wir.assigned_department, wir.assigned_user_id, wir.assigned_at, + wir.resolution_notes, wir.resolved_at, + wir.created_at, wir.updated_at, + u.username as reporter_name, u.name as reporter_full_name, + wc.category_name as factory_name, + w.workplace_name, + irc.category_type, irc.category_name as issue_category_name, + iri.item_name as issue_item_name, iri.severity, + assignee.username as assigned_user_name, assignee.name as assigned_full_name + FROM work_issue_reports wir + INNER JOIN users u ON wir.reporter_id = u.user_id + LEFT JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id + LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id + INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id + LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id + LEFT JOIN users assignee ON wir.assigned_user_id = assignee.user_id + WHERE 1=1 + `; - const params = []; + const params = []; - // 필터 적용 - if (filters.status) { - query += ` AND wir.status = ?`; - params.push(filters.status); - } - - if (filters.category_type) { - query += ` AND irc.category_type = ?`; - params.push(filters.category_type); - } - - if (filters.issue_category_id) { - query += ` AND wir.issue_category_id = ?`; - params.push(filters.issue_category_id); - } - - if (filters.factory_category_id) { - query += ` AND wir.factory_category_id = ?`; - params.push(filters.factory_category_id); - } - - if (filters.workplace_id) { - query += ` AND wir.workplace_id = ?`; - params.push(filters.workplace_id); - } - - if (filters.reporter_id) { - query += ` AND wir.reporter_id = ?`; - params.push(filters.reporter_id); - } - - if (filters.assigned_user_id) { - query += ` AND wir.assigned_user_id = ?`; - params.push(filters.assigned_user_id); - } - - if (filters.start_date && filters.end_date) { - query += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; - params.push(filters.start_date, filters.end_date); - } - - if (filters.search) { - query += ` AND (wir.additional_description LIKE ? OR iri.item_name LIKE ? OR wir.custom_location LIKE ?)`; - const searchTerm = `%${filters.search}%`; - params.push(searchTerm, searchTerm, searchTerm); - } - - query += ` ORDER BY wir.report_date DESC, wir.report_id DESC`; - - // 페이지네이션 - if (filters.limit) { - query += ` LIMIT ?`; - params.push(parseInt(filters.limit)); - - if (filters.offset) { - query += ` OFFSET ?`; - params.push(parseInt(filters.offset)); - } - } - - const [rows] = await db.query(query, params); - callback(null, rows); - } catch (err) { - callback(err); + if (filters.status) { + query += ` AND wir.status = ?`; + params.push(filters.status); } + + if (filters.category_type) { + query += ` AND irc.category_type = ?`; + params.push(filters.category_type); + } + + if (filters.issue_category_id) { + query += ` AND wir.issue_category_id = ?`; + params.push(filters.issue_category_id); + } + + if (filters.factory_category_id) { + query += ` AND wir.factory_category_id = ?`; + params.push(filters.factory_category_id); + } + + if (filters.workplace_id) { + query += ` AND wir.workplace_id = ?`; + params.push(filters.workplace_id); + } + + if (filters.reporter_id) { + query += ` AND wir.reporter_id = ?`; + params.push(filters.reporter_id); + } + + if (filters.assigned_user_id) { + query += ` AND wir.assigned_user_id = ?`; + params.push(filters.assigned_user_id); + } + + if (filters.start_date && filters.end_date) { + query += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; + params.push(filters.start_date, filters.end_date); + } + + if (filters.search) { + query += ` AND (wir.additional_description LIKE ? OR iri.item_name LIKE ? OR wir.custom_location LIKE ?)`; + const searchTerm = `%${filters.search}%`; + params.push(searchTerm, searchTerm, searchTerm); + } + + query += ` ORDER BY wir.report_date DESC, wir.report_id DESC`; + + if (filters.limit) { + query += ` LIMIT ?`; + params.push(parseInt(filters.limit)); + + if (filters.offset) { + query += ` OFFSET ?`; + params.push(parseInt(filters.offset)); + } + } + + const [rows] = await db.query(query, params); + return rows; }; -/** - * 신고 상세 조회 - */ -const getReportById = async (reportId, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT - wir.report_id, wir.reporter_id, wir.report_date, - wir.factory_category_id, wir.workplace_id, wir.custom_location, - wir.tbm_session_id, wir.visit_request_id, - wir.issue_category_id, wir.issue_item_id, wir.additional_description, - wir.photo_path1, wir.photo_path2, wir.photo_path3, wir.photo_path4, wir.photo_path5, - wir.status, wir.assigned_department, wir.assigned_user_id, wir.assigned_at, wir.assigned_by, - wir.resolution_notes, wir.resolution_photo_path1, wir.resolution_photo_path2, - wir.resolved_at, wir.resolved_by, - wir.modification_history, - wir.created_at, wir.updated_at, - u.username as reporter_name, u.name as reporter_full_name, - wc.category_name as factory_name, - w.workplace_name, - irc.category_type, irc.category_name as issue_category_name, - iri.item_name as issue_item_name, iri.severity, - assignee.username as assigned_user_name, assignee.name as assigned_full_name, - assigner.username as assigned_by_name, - resolver.username as resolved_by_name - FROM work_issue_reports wir - INNER JOIN users u ON wir.reporter_id = u.user_id - LEFT JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id - LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id - INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id - LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id - LEFT JOIN users assignee ON wir.assigned_user_id = assignee.user_id - LEFT JOIN users assigner ON wir.assigned_by = assigner.user_id - LEFT JOIN users resolver ON wir.resolved_by = resolver.user_id - WHERE wir.report_id = ?`, - [reportId] - ); +const getReportById = async (reportId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT + wir.report_id, wir.reporter_id, wir.report_date, + wir.factory_category_id, wir.workplace_id, wir.custom_location, + wir.tbm_session_id, wir.visit_request_id, + wir.issue_category_id, wir.issue_item_id, wir.additional_description, + wir.photo_path1, wir.photo_path2, wir.photo_path3, wir.photo_path4, wir.photo_path5, + wir.status, wir.assigned_department, wir.assigned_user_id, wir.assigned_at, wir.assigned_by, + wir.resolution_notes, wir.resolution_photo_path1, wir.resolution_photo_path2, + wir.resolved_at, wir.resolved_by, + wir.modification_history, + wir.created_at, wir.updated_at, + u.username as reporter_name, u.name as reporter_full_name, + wc.category_name as factory_name, + w.workplace_name, + irc.category_type, irc.category_name as issue_category_name, + iri.item_name as issue_item_name, iri.severity, + assignee.username as assigned_user_name, assignee.name as assigned_full_name, + assigner.username as assigned_by_name, + resolver.username as resolved_by_name + FROM work_issue_reports wir + INNER JOIN users u ON wir.reporter_id = u.user_id + LEFT JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id + LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id + INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id + LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id + LEFT JOIN users assignee ON wir.assigned_user_id = assignee.user_id + LEFT JOIN users assigner ON wir.assigned_by = assigner.user_id + LEFT JOIN users resolver ON wir.resolved_by = resolver.user_id + WHERE wir.report_id = ?`, + [reportId] + ); - callback(null, rows[0]); - } catch (err) { - callback(err); - } + return rows[0]; }; -/** - * 신고 수정 - */ -const updateReport = async (reportId, reportData, userId, callback) => { - try { - const db = await getDb(); +const updateReport = async (reportId, reportData, userId) => { + const db = await getDb(); - // 기존 데이터 조회 - const [existing] = await db.query( - `SELECT * FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [existing] = await db.query( + `SELECT * FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (existing.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - const current = existing[0]; - - // 수정 이력 생성 - const modifications = []; - const now = new Date().toISOString(); - - for (const key of Object.keys(reportData)) { - if (current[key] !== reportData[key] && reportData[key] !== undefined) { - modifications.push({ - field: key, - old_value: current[key], - new_value: reportData[key], - modified_at: now, - modified_by: userId - }); - } - } - - // 기존 이력과 병합 - const existingHistory = current.modification_history ? JSON.parse(current.modification_history) : []; - const newHistory = [...existingHistory, ...modifications]; - - const { - factory_category_id, - workplace_id, - custom_location, - issue_category_id, - issue_item_id, - additional_description, - photo_path1, - photo_path2, - photo_path3, - photo_path4, - photo_path5 - } = reportData; - - const [result] = await db.query( - `UPDATE work_issue_reports - SET factory_category_id = COALESCE(?, factory_category_id), - workplace_id = COALESCE(?, workplace_id), - custom_location = COALESCE(?, custom_location), - issue_category_id = COALESCE(?, issue_category_id), - issue_item_id = COALESCE(?, issue_item_id), - additional_description = COALESCE(?, additional_description), - photo_path1 = COALESCE(?, photo_path1), - photo_path2 = COALESCE(?, photo_path2), - photo_path3 = COALESCE(?, photo_path3), - photo_path4 = COALESCE(?, photo_path4), - photo_path5 = COALESCE(?, photo_path5), - modification_history = ?, - updated_at = NOW() - WHERE report_id = ?`, - [factory_category_id, workplace_id, custom_location, - issue_category_id, issue_item_id, additional_description, - photo_path1, photo_path2, photo_path3, photo_path4, photo_path5, - JSON.stringify(newHistory), reportId] - ); - - callback(null, result); - } catch (err) { - callback(err); + if (existing.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + const current = existing[0]; + + const modifications = []; + const now = new Date().toISOString(); + + for (const key of Object.keys(reportData)) { + if (current[key] !== reportData[key] && reportData[key] !== undefined) { + modifications.push({ + field: key, + old_value: current[key], + new_value: reportData[key], + modified_at: now, + modified_by: userId + }); + } + } + + const existingHistory = current.modification_history ? JSON.parse(current.modification_history) : []; + const newHistory = [...existingHistory, ...modifications]; + + const { + factory_category_id, + workplace_id, + custom_location, + issue_category_id, + issue_item_id, + additional_description, + photo_path1, + photo_path2, + photo_path3, + photo_path4, + photo_path5 + } = reportData; + + const [result] = await db.query( + `UPDATE work_issue_reports + SET factory_category_id = COALESCE(?, factory_category_id), + workplace_id = COALESCE(?, workplace_id), + custom_location = COALESCE(?, custom_location), + issue_category_id = COALESCE(?, issue_category_id), + issue_item_id = COALESCE(?, issue_item_id), + additional_description = COALESCE(?, additional_description), + photo_path1 = COALESCE(?, photo_path1), + photo_path2 = COALESCE(?, photo_path2), + photo_path3 = COALESCE(?, photo_path3), + photo_path4 = COALESCE(?, photo_path4), + photo_path5 = COALESCE(?, photo_path5), + modification_history = ?, + updated_at = NOW() + WHERE report_id = ?`, + [factory_category_id, workplace_id, custom_location, + issue_category_id, issue_item_id, additional_description, + photo_path1, photo_path2, photo_path3, photo_path4, photo_path5, + JSON.stringify(newHistory), reportId] + ); + + return result; }; -/** - * 신고 삭제 - */ -const deleteReport = async (reportId, callback) => { - try { - const db = await getDb(); +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 - FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [photos] = await db.query( + `SELECT photo_path1, photo_path2, photo_path3, photo_path4, photo_path5, + resolution_photo_path1, resolution_photo_path2 + FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - const [result] = await db.query( - `DELETE FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [result] = await db.query( + `DELETE FROM work_issue_reports WHERE report_id = ?`, + [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 db = await getDb(); +const receiveReport = async (reportId, userId) => { + const db = await getDb(); - // 현재 상태 확인 - const [current] = await db.query( - `SELECT status FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [current] = await db.query( + `SELECT status FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (current.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - if (current[0].status !== 'reported') { - return callback(new Error('접수 대기 상태가 아닙니다.')); - } - - const [result] = await db.query( - `UPDATE work_issue_reports - SET status = 'received', updated_at = NOW() - WHERE report_id = ?`, - [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); + if (current.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + if (current[0].status !== 'reported') { + throw new Error('접수 대기 상태가 아닙니다.'); + } + + const [result] = await db.query( + `UPDATE work_issue_reports + SET status = 'received', updated_at = NOW() + WHERE report_id = ?`, + [reportId] + ); + + await db.query( + `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) + VALUES (?, 'reported', 'received', ?)`, + [reportId, userId] + ); + + return result; }; -/** - * 담당자 배정 - */ -const assignReport = async (reportId, assignData, callback) => { - try { - const db = await getDb(); - const { assigned_department, assigned_user_id, assigned_by } = assignData; +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] - ); + const [current] = await db.query( + `SELECT status FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (current.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - // 접수 상태 이상이어야 배정 가능 - const validStatuses = ['received', 'in_progress']; - if (!validStatuses.includes(current[0].status)) { - return callback(new Error('접수된 상태에서만 담당자 배정이 가능합니다.')); - } - - const [result] = await db.query( - `UPDATE work_issue_reports - SET assigned_department = ?, assigned_user_id = ?, - assigned_at = NOW(), assigned_by = ?, updated_at = NOW() - WHERE report_id = ?`, - [assigned_department, assigned_user_id, assigned_by, reportId] - ); - - callback(null, result); - } catch (err) { - callback(err); + if (current.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + const validStatuses = ['received', 'in_progress']; + if (!validStatuses.includes(current[0].status)) { + throw new Error('접수된 상태에서만 담당자 배정이 가능합니다.'); + } + + const [result] = await db.query( + `UPDATE work_issue_reports + SET assigned_department = ?, assigned_user_id = ?, + assigned_at = NOW(), assigned_by = ?, updated_at = NOW() + WHERE report_id = ?`, + [assigned_department, assigned_user_id, assigned_by, reportId] + ); + + return result; }; -/** - * 처리 시작 (received → in_progress) - */ -const startProcessing = async (reportId, userId, callback) => { - try { - const db = await getDb(); +const startProcessing = async (reportId, userId) => { + const db = await getDb(); - // 현재 상태 확인 - const [current] = await db.query( - `SELECT status FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [current] = await db.query( + `SELECT status FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (current.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - if (current[0].status !== 'received') { - return callback(new Error('접수된 상태에서만 처리를 시작할 수 있습니다.')); - } - - const [result] = await db.query( - `UPDATE work_issue_reports - SET status = 'in_progress', updated_at = NOW() - WHERE report_id = ?`, - [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); + if (current.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + if (current[0].status !== 'received') { + throw new Error('접수된 상태에서만 처리를 시작할 수 있습니다.'); + } + + const [result] = await db.query( + `UPDATE work_issue_reports + SET status = 'in_progress', updated_at = NOW() + WHERE report_id = ?`, + [reportId] + ); + + await db.query( + `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) + VALUES (?, 'received', 'in_progress', ?)`, + [reportId, userId] + ); + + return result; }; -/** - * 처리 완료 (in_progress → completed) - */ -const completeReport = async (reportId, completionData, callback) => { - try { - const db = await getDb(); - const { resolution_notes, resolution_photo_path1, resolution_photo_path2, resolved_by } = completionData; +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] - ); + const [current] = await db.query( + `SELECT status FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (current.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - if (current[0].status !== 'in_progress') { - return callback(new Error('처리 중 상태에서만 완료할 수 있습니다.')); - } - - const [result] = await db.query( - `UPDATE work_issue_reports - SET status = 'completed', resolution_notes = ?, - resolution_photo_path1 = ?, resolution_photo_path2 = ?, - resolved_at = NOW(), resolved_by = ?, updated_at = NOW() - WHERE report_id = ?`, - [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); + if (current.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + if (current[0].status !== 'in_progress') { + throw new Error('처리 중 상태에서만 완료할 수 있습니다.'); + } + + const [result] = await db.query( + `UPDATE work_issue_reports + SET status = 'completed', resolution_notes = ?, + resolution_photo_path1 = ?, resolution_photo_path2 = ?, + resolved_at = NOW(), resolved_by = ?, updated_at = NOW() + WHERE report_id = ?`, + [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] + ); + + return result; }; -/** - * 신고 종료 (completed → closed) - */ -const closeReport = async (reportId, userId, callback) => { - try { - const db = await getDb(); +const closeReport = async (reportId, userId) => { + const db = await getDb(); - // 현재 상태 확인 - const [current] = await db.query( - `SELECT status FROM work_issue_reports WHERE report_id = ?`, - [reportId] - ); + const [current] = await db.query( + `SELECT status FROM work_issue_reports WHERE report_id = ?`, + [reportId] + ); - if (current.length === 0) { - return callback(new Error('신고를 찾을 수 없습니다.')); - } - - if (current[0].status !== 'completed') { - return callback(new Error('완료된 상태에서만 종료할 수 있습니다.')); - } - - const [result] = await db.query( - `UPDATE work_issue_reports - SET status = 'closed', updated_at = NOW() - WHERE report_id = ?`, - [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); + if (current.length === 0) { + throw new Error('신고를 찾을 수 없습니다.'); } + + if (current[0].status !== 'completed') { + throw new Error('완료된 상태에서만 종료할 수 있습니다.'); + } + + const [result] = await db.query( + `UPDATE work_issue_reports + SET status = 'closed', updated_at = NOW() + WHERE report_id = ?`, + [reportId] + ); + + await db.query( + `INSERT INTO work_issue_status_logs (report_id, previous_status, new_status, changed_by) + VALUES (?, 'completed', 'closed', ?)`, + [reportId, userId] + ); + + return result; }; -/** - * 상태 변경 이력 조회 - */ -const getStatusLogs = async (reportId, callback) => { - try { - const db = await getDb(); - const [rows] = await db.query( - `SELECT wisl.log_id, wisl.report_id, wisl.previous_status, wisl.new_status, - wisl.changed_by, wisl.change_reason, wisl.changed_at, - u.username as changed_by_name, u.name as changed_by_full_name - FROM work_issue_status_logs wisl - INNER JOIN users u ON wisl.changed_by = u.user_id - WHERE wisl.report_id = ? - ORDER BY wisl.changed_at ASC`, - [reportId] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +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, + wisl.changed_by, wisl.change_reason, wisl.changed_at, + u.username as changed_by_name, u.name as changed_by_full_name + FROM work_issue_status_logs wisl + INNER JOIN users u ON wisl.changed_by = u.user_id + WHERE wisl.report_id = ? + ORDER BY wisl.changed_at ASC`, + [reportId] + ); + return rows; }; // ==================== 통계 ==================== -/** - * 신고 통계 요약 - */ -const getStatsSummary = async (filters = {}, callback) => { - try { - const db = await getDb(); +const getStatsSummary = async (filters = {}) => { + const db = await getDb(); - let whereClause = '1=1'; - const params = []; + let whereClause = '1=1'; + const params = []; - if (filters.start_date && filters.end_date) { - whereClause += ` AND DATE(report_date) BETWEEN ? AND ?`; - params.push(filters.start_date, filters.end_date); - } - - if (filters.factory_category_id) { - whereClause += ` AND factory_category_id = ?`; - params.push(filters.factory_category_id); - } - - const [rows] = await db.query( - `SELECT - COUNT(*) as total, - SUM(CASE WHEN status = 'reported' THEN 1 ELSE 0 END) as reported, - SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received, - SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress, - SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed, - SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed - FROM work_issue_reports - WHERE ${whereClause}`, - params - ); - - callback(null, rows[0]); - } catch (err) { - callback(err); + if (filters.start_date && filters.end_date) { + whereClause += ` AND DATE(report_date) BETWEEN ? AND ?`; + params.push(filters.start_date, filters.end_date); } + + if (filters.factory_category_id) { + whereClause += ` AND factory_category_id = ?`; + params.push(filters.factory_category_id); + } + + const [rows] = await db.query( + `SELECT + COUNT(*) as total, + SUM(CASE WHEN status = 'reported' THEN 1 ELSE 0 END) as reported, + SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received, + SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed, + SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed + FROM work_issue_reports + WHERE ${whereClause}`, + params + ); + + return rows[0]; }; -/** - * 카테고리별 통계 - */ -const getStatsByCategory = async (filters = {}, callback) => { - try { - const db = await getDb(); +const getStatsByCategory = async (filters = {}) => { + const db = await getDb(); - let whereClause = '1=1'; - const params = []; + let whereClause = '1=1'; + const params = []; - if (filters.start_date && filters.end_date) { - whereClause += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; - params.push(filters.start_date, filters.end_date); - } - - const [rows] = await db.query( - `SELECT - irc.category_type, irc.category_name, - COUNT(*) as count - FROM work_issue_reports wir - INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id - WHERE ${whereClause} - GROUP BY irc.category_id - ORDER BY irc.category_type, count DESC`, - params - ); - - callback(null, rows); - } catch (err) { - callback(err); + if (filters.start_date && filters.end_date) { + whereClause += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; + params.push(filters.start_date, filters.end_date); } + + const [rows] = await db.query( + `SELECT + irc.category_type, irc.category_name, + COUNT(*) as count + FROM work_issue_reports wir + INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id + WHERE ${whereClause} + GROUP BY irc.category_id + ORDER BY irc.category_type, count DESC`, + params + ); + + return rows; }; -/** - * 작업장별 통계 - */ -const getStatsByWorkplace = async (filters = {}, callback) => { - try { - const db = await getDb(); +const getStatsByWorkplace = async (filters = {}) => { + const db = await getDb(); - let whereClause = 'wir.workplace_id IS NOT NULL'; - const params = []; + let whereClause = 'wir.workplace_id IS NOT NULL'; + const params = []; - if (filters.start_date && filters.end_date) { - whereClause += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; - params.push(filters.start_date, filters.end_date); - } - - if (filters.factory_category_id) { - whereClause += ` AND wir.factory_category_id = ?`; - params.push(filters.factory_category_id); - } - - const [rows] = await db.query( - `SELECT - wir.factory_category_id, wc.category_name as factory_name, - wir.workplace_id, w.workplace_name, - COUNT(*) as count - FROM work_issue_reports wir - INNER JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id - INNER JOIN workplaces w ON wir.workplace_id = w.workplace_id - WHERE ${whereClause} - GROUP BY wir.factory_category_id, wir.workplace_id - ORDER BY count DESC`, - params - ); - - callback(null, rows); - } catch (err) { - callback(err); + if (filters.start_date && filters.end_date) { + whereClause += ` AND DATE(wir.report_date) BETWEEN ? AND ?`; + params.push(filters.start_date, filters.end_date); } + + if (filters.factory_category_id) { + whereClause += ` AND wir.factory_category_id = ?`; + params.push(filters.factory_category_id); + } + + const [rows] = await db.query( + `SELECT + wir.factory_category_id, wc.category_name as factory_name, + wir.workplace_id, w.workplace_name, + COUNT(*) as count + FROM work_issue_reports wir + INNER JOIN workplace_categories wc ON wir.factory_category_id = wc.category_id + INNER JOIN workplaces w ON wir.workplace_id = w.workplace_id + WHERE ${whereClause} + GROUP BY wir.factory_category_id, wir.workplace_id + ORDER BY count DESC`, + params + ); + + return rows; }; module.exports = { diff --git a/system1-factory/api/models/workReportModel.js b/system1-factory/api/models/workReportModel.js index db0cf48..3766369 100644 --- a/system1-factory/api/models/workReportModel.js +++ b/system1-factory/api/models/workReportModel.js @@ -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,175 +41,146 @@ const createBatch = async (reports, callback) => { /** * 2. 단일 등록 */ -const create = async (report, callback) => { - try { - const db = await getDb(); - const { - date, worker_id, project_id, - task_id, overtime_hours, - work_details, memo - } = report; +const create = async (report) => { + const db = await getDb(); + const { + date, worker_id, project_id, + task_id, overtime_hours, + work_details, memo + } = report; - const [result] = await db.query( - `INSERT INTO WorkReports - (\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - [ - date, - worker_id, - project_id, - task_id || null, - overtime_hours || null, - work_details || null, - memo || null - ] - ); + const [result] = await db.query( + `INSERT INTO WorkReports + (\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [ + date, + worker_id, + project_id, + task_id || null, + overtime_hours || null, + work_details || null, + memo || null + ] + ); - callback(null, result.insertId); - } catch (err) { - callback(err); - } + return result.insertId; }; - /** - * 3. 날짜별 조회 - */ - const getAllByDate = async (date, callback) => { - try { - const db = await getDb(); - const sql = ` - SELECT - wr.worker_id, -- 이 줄을 추가했습니다 - wr.id, - wr.\`date\`, - w.worker_name, - p.project_name, - CONCAT(t.category, ':', t.subcategory) AS task_name, - wr.overtime_hours, - wr.work_details, - wr.memo - FROM WorkReports wr - LEFT JOIN workers w ON wr.worker_id = w.worker_id - LEFT JOIN projects p ON wr.project_id = p.project_id - LEFT JOIN Tasks t ON wr.task_id = t.task_id - WHERE wr.\`date\` = ? - ORDER BY w.worker_name ASC - `; - const [rows] = await db.query(sql, [date]); - callback(null, rows); - } catch (err) { - callback(err); - } - }; +/** + * 3. 날짜별 조회 + */ +const getAllByDate = async (date) => { + const db = await getDb(); + const sql = ` + SELECT + wr.worker_id, + wr.id, + wr.\`date\`, + w.worker_name, + p.project_name, + CONCAT(t.category, ':', t.subcategory) AS task_name, + wr.overtime_hours, + wr.work_details, + wr.memo + FROM WorkReports wr + LEFT JOIN workers w ON wr.worker_id = w.worker_id + LEFT JOIN projects p ON wr.project_id = p.project_id + LEFT JOIN Tasks t ON wr.task_id = t.task_id + WHERE wr.\`date\` = ? + ORDER BY w.worker_name ASC + `; + const [rows] = await db.query(sql, [date]); + return rows; +}; /** * 4. 기간 조회 */ -const getByRange = async (start, end, callback) => { - try { - 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 \`date\` BETWEEN ? AND ? - ORDER BY \`date\` ASC`, - [start, end] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +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 + WHERE \`date\` BETWEEN ? AND ? + ORDER BY \`date\` ASC`, + [start, end] + ); + return rows; }; /** * 5. ID로 조회 */ -const getById = async (id, callback) => { - try { - 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); - } +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] + ); + return rows[0]; }; /** * 6. 수정 */ -const update = async (id, report, callback) => { - try { - const db = await getDb(); - const { - date, worker_id, project_id, - task_id, overtime_hours, - work_details, memo - } = report; +const update = async (id, report) => { + const db = await getDb(); + const { + date, worker_id, project_id, + task_id, overtime_hours, + work_details, memo + } = report; - const [result] = await db.query( - `UPDATE WorkReports - SET \`date\` = ?, - worker_id = ?, - project_id = ?, - task_id = ?, - overtime_hours = ?, - work_details = ?, - memo = ?, - updated_at = CURRENT_TIMESTAMP - WHERE id = ?`, - [ - date, - worker_id, - project_id, - task_id || null, - overtime_hours || null, - work_details || null, - memo || null, - id - ] - ); + const [result] = await db.query( + `UPDATE WorkReports + SET \`date\` = ?, + worker_id = ?, + project_id = ?, + task_id = ?, + overtime_hours = ?, + work_details = ?, + memo = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [ + date, + worker_id, + project_id, + task_id || null, + overtime_hours || null, + work_details || null, + memo || null, + id + ] + ); - callback(null, result.affectedRows); - } catch (err) { - callback(err); - } + return result.affectedRows; }; /** * 7. 삭제 */ -const remove = async (id, callback) => { - try { - 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))); - } +const remove = async (id) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM WorkReports WHERE id = ?`, + [id] + ); + return result.affectedRows; }; /** * 8. 중복 확인 */ -const existsByDateAndWorker = async (date, worker_id, callback) => { - try { - 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); - } +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] + ); + return rows.length > 0; }; -// ✅ 내보내기 module.exports = { create, createBatch, @@ -220,4 +190,4 @@ module.exports = { update, remove, existsByDateAndWorker -}; \ No newline at end of file +}; diff --git a/system1-factory/api/models/workplaceModel.js b/system1-factory/api/models/workplaceModel.js index 4a7e754..e3239a1 100644 --- a/system1-factory/api/models/workplaceModel.js +++ b/system1-factory/api/models/workplaceModel.js @@ -2,426 +2,290 @@ const { getDb } = require('../dbPool'); // ==================== 카테고리(공장) 관련 ==================== -/** - * 카테고리 생성 - */ -const createCategory = async (category, callback) => { - try { - const db = await getDb(); - const { - category_name, - description = null, - display_order = 0, - is_active = true - } = category; +const createCategory = async (category) => { + const db = await getDb(); + const { + category_name, + description = null, + display_order = 0, + is_active = true + } = category; - const [result] = await db.query( - `INSERT INTO workplace_categories - (category_name, description, display_order, is_active) - VALUES (?, ?, ?, ?)`, - [category_name, description, display_order, is_active] - ); + const [result] = await db.query( + `INSERT INTO workplace_categories + (category_name, description, display_order, is_active) + VALUES (?, ?, ?, ?)`, + [category_name, description, display_order, is_active] + ); - callback(null, result.insertId); - } catch (err) { - callback(err); - } + return result.insertId; }; -/** - * 모든 카테고리 조회 - */ -const getAllCategories = async (callback) => { - try { - 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); - } +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` + ); + return rows; }; -/** - * 활성 카테고리만 조회 - */ -const getActiveCategories = async (callback) => { - try { - 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 - WHERE is_active = TRUE - ORDER BY display_order ASC, category_id ASC` - ); - callback(null, rows); - } catch (err) { - callback(err); - } +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 + FROM workplace_categories + WHERE is_active = TRUE + ORDER BY display_order ASC, category_id ASC` + ); + return rows; }; -/** - * ID로 카테고리 조회 - */ -const getCategoryById = async (categoryId, callback) => { - try { - 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 - WHERE category_id = ?`, - [categoryId] - ); - callback(null, rows[0]); - } catch (err) { - callback(err); - } +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 + FROM workplace_categories + WHERE category_id = ?`, + [categoryId] + ); + return rows[0]; }; -/** - * 카테고리 수정 - */ -const updateCategory = async (categoryId, category, callback) => { - try { - const db = await getDb(); - const { - category_name, - description, - display_order, - is_active, - layout_image - } = category; +const updateCategory = async (categoryId, category) => { + const db = await getDb(); + const { + category_name, + description, + display_order, + is_active, + layout_image + } = category; - const [result] = await db.query( - `UPDATE workplace_categories - SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW() - WHERE category_id = ?`, - [category_name, description, display_order, is_active, layout_image, categoryId] - ); + const [result] = await db.query( + `UPDATE workplace_categories + SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW() + WHERE category_id = ?`, + [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 db = await getDb(); - const [result] = await db.query( - `DELETE FROM workplace_categories WHERE category_id = ?`, - [categoryId] - ); - callback(null, result); - } catch (err) { - callback(err); - } +const deleteCategory = async (categoryId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplace_categories WHERE category_id = ?`, + [categoryId] + ); + return result; }; // ==================== 작업장 관련 ==================== -/** - * 작업장 생성 - */ -const createWorkplace = async (workplace, callback) => { - try { - const db = await getDb(); - const { - category_id = null, - workplace_name, - description = null, - is_active = true, - workplace_purpose = null, - display_priority = 0 - } = workplace; +const createWorkplace = async (workplace) => { + const db = await getDb(); + const { + category_id = null, + workplace_name, + description = null, + is_active = true, + workplace_purpose = null, + display_priority = 0 + } = workplace; - const [result] = await db.query( - `INSERT INTO workplaces - (category_id, workplace_name, description, is_active, workplace_purpose, display_priority) - VALUES (?, ?, ?, ?, ?, ?)`, - [category_id, workplace_name, description, is_active, workplace_purpose, display_priority] - ); + const [result] = await db.query( + `INSERT INTO workplaces + (category_id, workplace_name, description, is_active, workplace_purpose, display_priority) + VALUES (?, ?, ?, ?, ?, ?)`, + [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 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, - w.layout_image, w.created_at, w.updated_at, - wc.category_name - FROM workplaces w - 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); - } +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, + w.layout_image, w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + 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` + ); + return rows; }; -/** - * 활성 작업장만 조회 - */ -const getActiveWorkplaces = async (callback) => { - try { - 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, - w.layout_image, w.created_at, w.updated_at, - wc.category_name - FROM workplaces w - LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id - WHERE w.is_active = TRUE - ORDER BY wc.display_order ASC, w.workplace_id DESC` - ); - callback(null, rows); - } catch (err) { - callback(err); - } +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, + w.layout_image, w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.is_active = TRUE + ORDER BY wc.display_order ASC, w.workplace_id DESC` + ); + return rows; }; -/** - * 카테고리별 작업장 조회 - */ -const getWorkplacesByCategory = async (categoryId, callback) => { - try { - 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, - w.layout_image, w.created_at, w.updated_at, - wc.category_name - FROM workplaces w - LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id - WHERE w.category_id = ? - ORDER BY w.workplace_id DESC`, - [categoryId] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +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, + w.layout_image, w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.category_id = ? + ORDER BY w.workplace_id DESC`, + [categoryId] + ); + return rows; }; -/** - * ID로 작업장 조회 - */ -const getWorkplaceById = async (workplaceId, callback) => { - try { - 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, - w.layout_image, w.created_at, w.updated_at, - wc.category_name - FROM workplaces w - LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id - WHERE w.workplace_id = ?`, - [workplaceId] - ); - callback(null, rows[0]); - } catch (err) { - callback(err); - } +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, + w.layout_image, w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.workplace_id = ?`, + [workplaceId] + ); + return rows[0]; }; -/** - * 작업장 수정 - */ -const updateWorkplace = async (workplaceId, workplace, callback) => { - try { - const db = await getDb(); - const { - category_id, - workplace_name, - description, - is_active, - workplace_purpose, - display_priority, - layout_image - } = workplace; +const updateWorkplace = async (workplaceId, workplace) => { + const db = await getDb(); + const { + category_id, + workplace_name, + description, + is_active, + workplace_purpose, + display_priority, + layout_image + } = workplace; - const [result] = await db.query( - `UPDATE workplaces - SET category_id = ?, workplace_name = ?, description = ?, is_active = ?, - workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW() - WHERE workplace_id = ?`, - [category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId] - ); + const [result] = await db.query( + `UPDATE workplaces + SET category_id = ?, workplace_name = ?, description = ?, is_active = ?, + workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW() + WHERE workplace_id = ?`, + [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 db = await getDb(); - const [result] = await db.query( - `DELETE FROM workplaces WHERE workplace_id = ?`, - [workplaceId] - ); - callback(null, result); - } catch (err) { - callback(err); - } +const deleteWorkplace = async (workplaceId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplaces WHERE workplace_id = ?`, + [workplaceId] + ); + return result; }; // ==================== 작업장 지도 영역 관련 ==================== -/** - * 작업장 지도 영역 생성 - */ -const createMapRegion = async (region, callback) => { - try { - const db = await getDb(); - const { - workplace_id, - category_id, - x_start, - y_start, - x_end, - y_end, - shape = 'rect', - polygon_points = null - } = region; +const createMapRegion = async (region) => { + const db = await getDb(); + const { + workplace_id, + category_id, + x_start, + y_start, + x_end, + y_end, + shape = 'rect', + polygon_points = null + } = region; - const [result] = await db.query( - `INSERT INTO workplace_map_regions - (workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - [workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points] - ); + const [result] = await db.query( + `INSERT INTO workplace_map_regions + (workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [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 db = await getDb(); - const [rows] = await db.query( - `SELECT mr.*, w.workplace_name, w.description - FROM workplace_map_regions mr - INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id - WHERE mr.category_id = ? AND w.is_active = TRUE - ORDER BY mr.region_id ASC`, - [categoryId] - ); - callback(null, rows); - } catch (err) { - callback(err); - } +const getMapRegionsByCategory = async (categoryId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT mr.*, w.workplace_name, w.description + FROM workplace_map_regions mr + INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id + WHERE mr.category_id = ? AND w.is_active = TRUE + ORDER BY mr.region_id ASC`, + [categoryId] + ); + return rows; }; -/** - * 작업장별 지도 영역 조회 - */ -const getMapRegionByWorkplace = async (workplaceId, callback) => { - try { - 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); - } +const getMapRegionByWorkplace = async (workplaceId) => { + const db = await getDb(); + const [rows] = await db.query( + `SELECT * FROM workplace_map_regions WHERE workplace_id = ?`, + [workplaceId] + ); + return rows[0]; }; -/** - * 지도 영역 수정 - */ -const updateMapRegion = async (regionId, region, callback) => { - try { - const db = await getDb(); - const { - x_start, - y_start, - x_end, - y_end, - shape, - polygon_points - } = region; +const updateMapRegion = async (regionId, region) => { + const db = await getDb(); + const { + x_start, + y_start, + x_end, + y_end, + shape, + polygon_points + } = region; - const [result] = await db.query( - `UPDATE workplace_map_regions - SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW() - WHERE region_id = ?`, - [x_start, y_start, x_end, y_end, shape, polygon_points, regionId] - ); + const [result] = await db.query( + `UPDATE workplace_map_regions + SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW() + WHERE region_id = ?`, + [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 db = await getDb(); - const [result] = await db.query( - `DELETE FROM workplace_map_regions WHERE region_id = ?`, - [regionId] - ); - callback(null, result); - } catch (err) { - callback(err); - } +const deleteMapRegion = async (regionId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplace_map_regions WHERE region_id = ?`, + [regionId] + ); + return result; }; -/** - * 작업장 영역 일괄 삭제 (카테고리별) - */ -const deleteMapRegionsByCategory = async (categoryId, callback) => { - try { - 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); - } +const deleteMapRegionsByCategory = async (categoryId) => { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplace_map_regions WHERE category_id = ?`, + [categoryId] + ); + 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, diff --git a/system1-factory/api/services/workReportService.js b/system1-factory/api/services/workReportService.js index f1b0daf..6e88727 100644 --- a/system1-factory/api/services/workReportService.js +++ b/system1-factory/api/services/workReportService.js @@ -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, diff --git a/system1-factory/api/tests/unit/services/workReportService.test.js b/system1-factory/api/tests/unit/services/workReportService.test.js index 7567d31..aab243a 100644 --- a/system1-factory/api/tests/unit/services/workReportService.test.js +++ b/system1-factory/api/tests/unit/services/workReportService.test.js @@ -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);