feat: 설비 상세 패널 및 임시 이동 기능 구현

- 설비 마커 클릭 시 슬라이드 패널로 상세 정보 표시
- 설비 사진 업로드/삭제 기능
- 설비 임시 이동 기능 (3단계 지도 기반 선택)
  - Step 1: 공장 선택
  - Step 2: 레이아웃 지도에서 작업장 선택
  - Step 3: 상세 지도에서 위치 선택
- 설비 외부 반출/반입 기능
- 설비 수리 신청 기능 (기존 신고 시스템 연동)
- DB 마이그레이션 추가 (사진, 임시이동, 외부반출 테이블)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-04 13:45:56 +09:00
parent 90d3e32992
commit 4d83f10b07
13 changed files with 6043 additions and 74 deletions

View File

@@ -1,5 +1,6 @@
// controllers/equipmentController.js
const EquipmentModel = require('../models/equipmentModel');
const imageUploadService = require('../services/imageUploadService');
const EquipmentController = {
// CREATE - 설비 생성
@@ -379,6 +380,529 @@ const EquipmentController = {
message: '서버 오류가 발생했습니다.'
});
}
},
// ==========================================
// 설비 사진 관리
// ==========================================
// ADD PHOTO - 설비 사진 추가
addPhoto: async (req, res) => {
try {
const equipmentId = req.params.id;
const { photo_base64, description, display_order } = req.body;
if (!photo_base64) {
return res.status(400).json({
success: false,
message: '사진 데이터가 필요합니다.'
});
}
// Base64 이미지를 파일로 저장
const photoPath = await imageUploadService.saveBase64Image(
photo_base64,
'equipment',
'equipments'
);
if (!photoPath) {
return res.status(500).json({
success: false,
message: '사진 저장에 실패했습니다.'
});
}
// DB에 사진 정보 저장
const photoData = {
photo_path: photoPath,
description: description || null,
display_order: display_order || 0,
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
});
});
} catch (error) {
console.error('사진 추가 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET PHOTOS - 설비 사진 조회
getPhotos: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// DELETE PHOTO - 설비 사진 삭제
deletePhoto: async (req, res) => {
try {
const photoId = 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);
}
res.json({
success: true,
message: '사진이 성공적으로 삭제되었습니다.',
data: { photo_id: photoId }
});
});
} catch (error) {
console.error('사진 삭제 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// ==========================================
// 설비 임시 이동
// ==========================================
// MOVE TEMPORARILY - 설비 임시 이동
moveTemporarily: (req, res) => {
try {
const equipmentId = req.params.id;
const moveData = {
target_workplace_id: req.body.target_workplace_id,
target_x_percent: req.body.target_x_percent,
target_y_percent: req.body.target_y_percent,
target_width_percent: req.body.target_width_percent,
target_height_percent: req.body.target_height_percent,
from_workplace_id: req.body.from_workplace_id,
from_x_percent: req.body.from_x_percent,
from_y_percent: req.body.from_y_percent,
reason: req.body.reason,
moved_by: req.user?.user_id || null
};
if (!moveData.target_workplace_id || moveData.target_x_percent === undefined || moveData.target_y_percent === undefined) {
return res.status(400).json({
success: false,
message: '이동할 작업장과 위치가 필요합니다.'
});
}
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
});
});
} catch (error) {
console.error('설비 이동 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// RETURN TO ORIGINAL - 설비 원위치 복귀
returnToOriginal: (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
});
});
} catch (error) {
console.error('설비 복귀 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET TEMPORARILY MOVED - 임시 이동된 설비 목록
getTemporarilyMoved: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET MOVE LOGS - 설비 이동 이력 조회
getMoveLogs: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// ==========================================
// 설비 외부 반출/반입
// ==========================================
// EXPORT EQUIPMENT - 설비 외부 반출
exportEquipment: (req, res) => {
try {
const equipmentId = req.params.id;
const exportData = {
equipment_id: equipmentId,
export_date: req.body.export_date,
expected_return_date: req.body.expected_return_date,
destination: req.body.destination,
reason: req.body.reason,
notes: req.body.notes,
is_repair: req.body.is_repair || false,
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
});
});
} catch (error) {
console.error('설비 반출 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// RETURN EQUIPMENT - 설비 반입 (외부에서 복귀)
returnEquipment: (req, res) => {
try {
const logId = req.params.logId;
const returnData = {
return_date: req.body.return_date,
new_status: req.body.new_status || 'active',
notes: req.body.notes,
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
});
});
} catch (error) {
console.error('설비 반입 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET EXTERNAL LOGS - 설비 외부 반출 이력 조회
getExternalLogs: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET EXPORTED EQUIPMENTS - 현재 외부 반출 중인 설비 목록
getExportedEquipments: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// ==========================================
// 설비 수리 신청
// ==========================================
// 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) {
const path = await imageUploadService.saveBase64Image(base64, 'repair', 'issues');
if (path) photoPaths.push(path);
}
}
const requestData = {
equipment_id: equipmentId,
item_id: item_id || null,
workplace_id: workplace_id || null,
description: description || null,
photo_paths: photoPaths.length > 0 ? photoPaths : null,
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
});
});
} catch (error) {
console.error('수리 신청 오류:', error);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET REPAIR HISTORY - 설비 수리 이력 조회
getRepairHistory: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
},
// GET REPAIR CATEGORIES - 설비 수리 항목 목록 조회
getRepairCategories: (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);
res.status(500).json({
success: false,
message: '서버 오류가 발생했습니다.'
});
}
}
};