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>
This commit is contained in:
201
tksafety/api/controllers/riskController.js
Normal file
201
tksafety/api/controllers/riskController.js
Normal file
@@ -0,0 +1,201 @@
|
||||
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 내보내기 실패' });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user