/** * 작업 중 문제 신고 컨트롤러 */ const workIssueModel = require('../models/workIssueModel'); const imageUploadService = require('../services/imageUploadService'); // ==================== 신고 카테고리 관리 ==================== /** * 모든 카테고리 조회 */ exports.getAllCategories = (req, res) => { workIssueModel.getAllCategories((err, categories) => { if (err) { console.error('카테고리 조회 실패:', err); return res.status(500).json({ success: false, error: '카테고리 조회 실패' }); } res.json({ success: true, data: categories }); }); }; /** * 타입별 카테고리 조회 */ exports.getCategoriesByType = (req, res) => { const { type } = req.params; if (!['nonconformity', 'safety'].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: '카테고리 조회 실패' }); } 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: '카테고리 타입과 이름은 필수입니다.' }); } 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; 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: '카테고리가 수정되었습니다.' }); } ); }; /** * 카테고리 삭제 */ exports.deleteCategory = (req, res) => { const { id } = req.params; workIssueModel.deleteCategory(id, (err, result) => { if (err) { console.error('카테고리 삭제 실패:', err); return res.status(500).json({ success: false, error: '카테고리 삭제 실패' }); } res.json({ success: true, message: '카테고리가 삭제되었습니다.' }); }); }; // ==================== 사전 정의 항목 관리 ==================== /** * 카테고리별 항목 조회 */ 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: '항목 조회 실패' }); } 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와 항목명은 필수입니다.' }); } 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.deleteItem = (req, res) => { const { id } = req.params; workIssueModel.deleteItem(id, (err, result) => { if (err) { console.error('항목 삭제 실패:', err); return res.status(500).json({ success: false, error: '항목 삭제 실패' }); } res.json({ success: true, message: '항목이 삭제되었습니다.' }); }); }; // ==================== 문제 신고 관리 ==================== /** * 신고 생성 */ exports.createReport = async (req, res) => { try { const { factory_category_id, workplace_id, custom_location, tbm_session_id, visit_request_id, issue_category_id, issue_item_id, additional_description, photos = [] } = req.body; const reporter_id = req.user.user_id; if (!issue_category_id) { return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' }); } // 위치 정보 검증 (지도 선택 또는 기타 위치) if (!factory_category_id && !custom_location) { return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' }); } // 사진 저장 (최대 5장) const photoPaths = { photo_path1: null, photo_path2: null, photo_path3: null, photo_path4: null, photo_path5: null }; for (let i = 0; i < Math.min(photos.length, 5); i++) { if (photos[i]) { const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue'); if (savedPath) { photoPaths[`photo_path${i + 1}`] = savedPath; } } } const reportData = { reporter_id, factory_category_id: factory_category_id || null, workplace_id: workplace_id || null, custom_location: custom_location || null, tbm_session_id: tbm_session_id || null, visit_request_id: visit_request_id || null, issue_category_id, issue_item_id: issue_item_id || null, additional_description: additional_description || null, ...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 } }); }); } catch (error) { console.error('신고 생성 에러:', error); 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 }; // 일반 사용자는 자신의 신고만 조회 (관리자 제외) 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: '신고 목록 조회 실패' }); } res.json({ success: true, data: reports }); }); }; /** * 신고 상세 조회 */ 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: '신고 상세 조회 실패' }); } 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; const isManager = ['admin', 'system', 'support_team'].includes(userLevel); if (!isOwner && !isAssignee && !isManager) { return res.status(403).json({ success: false, error: '권한이 없습니다.' }); } res.json({ success: true, data: report }); }); }; /** * 신고 수정 */ 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: '신고 조회 실패' }); } 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: '삭제 권한이 없습니다.' }); } workIssueModel.deleteReport(id, async (deleteErr, { result, photos }) => { if (deleteErr) { console.error('신고 삭제 실패:', deleteErr); return res.status(500).json({ success: false, error: '신고 삭제 실패' }); } // 사진 파일 삭제 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: '신고가 삭제되었습니다.' }); }); }); }; // ==================== 상태 관리 ==================== /** * 신고 접수 */ 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 || '신고 접수 실패' }); } 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: '담당자는 필수입니다.' }); } workIssueModel.assignReport(id, { assigned_department, assigned_user_id, assigned_by: req.user.user_id }, (err, result) => { if (err) { console.error('담당자 배정 실패:', err); return res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' }); } res.json({ success: true, message: '담당자가 배정되었습니다.' }); }); }; /** * 처리 시작 */ exports.startProcessing = (req, res) => { const { id } = req.params; workIssueModel.startProcessing(id, req.user.user_id, (err, result) => { if (err) { console.error('처리 시작 실패:', err); return res.status(400).json({ success: false, error: err.message || '처리 시작 실패' }); } res.json({ success: true, message: '처리가 시작되었습니다.' }); }); }; /** * 처리 완료 */ exports.completeReport = async (req, res) => { try { const { id } = req.params; const { resolution_notes, resolution_photos = [] } = req.body; // 완료 사진 저장 let resolution_photo_path1 = null; let resolution_photo_path2 = null; if (resolution_photos[0]) { resolution_photo_path1 = await imageUploadService.saveBase64Image(resolution_photos[0], 'resolution'); } if (resolution_photos[1]) { resolution_photo_path2 = await imageUploadService.saveBase64Image(resolution_photos[1], 'resolution'); } 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: '서버 오류가 발생했습니다.' }); } }; /** * 신고 종료 */ 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 || '신고 종료 실패' }); } res.json({ success: true, 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: '상태 이력 조회 실패' }); } res.json({ success: true, data: logs }); }); }; // ==================== 통계 ==================== /** * 통계 요약 */ 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: '통계 조회 실패' }); } res.json({ success: true, data: stats }); }); }; /** * 카테고리별 통계 */ 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: '통계 조회 실패' }); } res.json({ success: true, data: stats }); }); }; /** * 작업장별 통계 */ 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: '통계 조회 실패' }); } res.json({ success: true, data: stats }); }); };