Files
tk-factory-services/tksafety/api/controllers/riskController.js
Hyungi Ahn e9b69ed87b feat(tksafety): 위험성평가 모듈 Phase 1 구현 — DB·API·Excel·프론트엔드
5개 테이블(risk_projects/processes/items/mitigations/templates) + 마스터 시딩,
프로젝트·항목·감소대책 CRUD API, ExcelJS 평가표 내보내기,
프로젝트 목록·평가 수행 페이지, 사진 업로드(multer), 네비게이션·CSS 추가.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 08:05:19 +09:00

202 lines
8.2 KiB
JavaScript

const riskModel = require('../models/riskModel');
// ==================== 공정 템플릿 ====================
exports.getTemplates = async (req, res) => {
try {
const templates = await riskModel.getTemplates(req.query.product_type);
res.json({ success: true, data: templates });
} catch (err) {
console.error('템플릿 조회 오류:', err);
res.status(500).json({ success: false, error: '템플릿 조회 실패' });
}
};
// ==================== 프로젝트 CRUD ====================
exports.createProject = async (req, res) => {
try {
const { title, product_type } = req.body;
if (!title || !product_type) {
return res.status(400).json({ success: false, error: '제목과 제품유형은 필수입니다' });
}
const data = { ...req.body, created_by: req.user.user_id || req.user.id };
const projectId = await riskModel.createProject(data);
res.status(201).json({ success: true, message: '프로젝트가 생성되었습니다', data: { id: projectId } });
} catch (err) {
console.error('프로젝트 생성 오류:', err);
res.status(500).json({ success: false, error: '프로젝트 생성 실패' });
}
};
exports.getAllProjects = async (req, res) => {
try {
const projects = await riskModel.getAllProjects(req.query);
// 대시보드 요약
const summary = {
total: projects.length,
risk_distribution: { high: 0, substantial: 0, moderate: 0, low: 0 }
};
for (const p of projects) {
summary.risk_distribution.high += p.high_risk_count || 0;
}
res.json({ success: true, data: { projects, summary } });
} catch (err) {
console.error('프로젝트 목록 조회 오류:', err);
res.status(500).json({ success: false, error: '프로젝트 목록 조회 실패' });
}
};
exports.getProjectById = async (req, res) => {
try {
const project = await riskModel.getProjectById(req.params.id);
if (!project) return res.status(404).json({ success: false, error: '프로젝트를 찾을 수 없습니다' });
res.json({ success: true, data: project });
} catch (err) {
console.error('프로젝트 상세 조회 오류:', err);
res.status(500).json({ success: false, error: '프로젝트 상세 조회 실패' });
}
};
exports.updateProject = async (req, res) => {
try {
const result = await riskModel.updateProject(req.params.id, req.body);
if (result.affectedRows === 0) return res.status(404).json({ success: false, error: '프로젝트를 찾을 수 없습니다' });
res.json({ success: true, message: '프로젝트가 수정되었습니다' });
} catch (err) {
console.error('프로젝트 수정 오류:', err);
res.status(500).json({ success: false, error: '프로젝트 수정 실패' });
}
};
exports.deleteProject = async (req, res) => {
try {
const result = await riskModel.deleteProject(req.params.id);
if (result.affectedRows === 0) return res.status(404).json({ success: false, error: '프로젝트를 찾을 수 없습니다' });
res.json({ success: true, message: '프로젝트가 삭제되었습니다' });
} catch (err) {
console.error('프로젝트 삭제 오류:', err);
res.status(500).json({ success: false, error: '프로젝트 삭제 실패' });
}
};
// ==================== 세부 공정 ====================
exports.addProcess = async (req, res) => {
try {
const { process_name } = req.body;
if (!process_name) return res.status(400).json({ success: false, error: '공정명은 필수입니다' });
const processId = await riskModel.addProcess(req.params.id, req.body);
res.status(201).json({ success: true, message: '공정이 추가되었습니다', data: { id: processId } });
} catch (err) {
console.error('공정 추가 오류:', err);
res.status(500).json({ success: false, error: '공정 추가 실패' });
}
};
// ==================== 평가 항목 CRUD ====================
exports.createItem = async (req, res) => {
try {
const itemId = await riskModel.createItem(req.params.processId, req.body);
res.status(201).json({ success: true, message: '항목이 추가되었습니다', data: { id: itemId } });
} catch (err) {
console.error('항목 추가 오류:', err);
res.status(500).json({ success: false, error: '항목 추가 실패' });
}
};
exports.updateItem = async (req, res) => {
try {
const result = await riskModel.updateItem(req.params.itemId, req.body);
if (result.affectedRows === 0) return res.status(404).json({ success: false, error: '항목을 찾을 수 없습니다' });
res.json({ success: true, message: '항목이 수정되었습니다' });
} catch (err) {
console.error('항목 수정 오류:', err);
res.status(500).json({ success: false, error: '항목 수정 실패' });
}
};
exports.deleteItem = async (req, res) => {
try {
const result = await riskModel.deleteItem(req.params.itemId);
if (result.affectedRows === 0) return res.status(404).json({ success: false, error: '항목을 찾을 수 없습니다' });
res.json({ success: true, message: '항목이 삭제되었습니다' });
} catch (err) {
console.error('항목 삭제 오류:', err);
res.status(500).json({ success: false, error: '항목 삭제 실패' });
}
};
// ==================== 감소대책 CRUD ====================
exports.getMitigations = async (req, res) => {
try {
const mitigations = await riskModel.getMitigationsByProject(req.params.id);
res.json({ success: true, data: mitigations });
} catch (err) {
console.error('감소대책 조회 오류:', err);
res.status(500).json({ success: false, error: '감소대책 조회 실패' });
}
};
exports.createMitigation = async (req, res) => {
try {
const { mitigation_no } = req.body;
if (!mitigation_no) return res.status(400).json({ success: false, error: '대책 번호는 필수입니다' });
const mitigationId = await riskModel.createMitigation(req.params.id, req.body);
res.status(201).json({ success: true, message: '감소대책이 추가되었습니다', data: { id: mitigationId } });
} catch (err) {
if (err.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ success: false, error: '이미 존재하는 대책 번호입니다' });
}
console.error('감소대책 생성 오류:', err);
res.status(500).json({ success: false, error: '감소대책 생성 실패' });
}
};
exports.updateMitigation = async (req, res) => {
try {
const result = await riskModel.updateMitigation(req.params.mitigationId, req.body);
if (result.affectedRows === 0) return res.status(404).json({ success: false, error: '감소대책을 찾을 수 없습니다' });
res.json({ success: true, message: '감소대책이 수정되었습니다' });
} catch (err) {
console.error('감소대책 수정 오류:', err);
res.status(500).json({ success: false, error: '감소대책 수정 실패' });
}
};
exports.uploadPhoto = async (req, res) => {
try {
if (!req.file) return res.status(400).json({ success: false, error: '사진 파일이 없습니다' });
const photoPath = '/uploads/risk/' + req.file.filename;
await riskModel.updateMitigationPhoto(req.params.mitigationId, photoPath);
res.json({ success: true, message: '사진이 업로드되었습니다', data: { photo_url: photoPath } });
} catch (err) {
console.error('사진 업로드 오류:', err);
res.status(500).json({ success: false, error: '사진 업로드 실패' });
}
};
// ==================== Excel 내보내기 ====================
exports.exportExcel = async (req, res) => {
try {
const project = await riskModel.getProjectById(req.params.id);
if (!project) return res.status(404).json({ success: false, error: '프로젝트를 찾을 수 없습니다' });
const { generateRiskExcel } = require('../utils/riskExcelExport');
const buffer = await generateRiskExcel(project);
const filename = encodeURIComponent(`위험성평가_${project.title}_${project.year}.xlsx`);
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${filename}`);
res.send(buffer);
} catch (err) {
console.error('Excel 내보내기 오류:', err);
res.status(500).json({ success: false, error: 'Excel 내보내기 실패' });
}
};