feat(tksafety): 통합 출입신고 관리 시스템 구현
- DB 마이그레이션: request_type, visitor_name, department_id, check_in/out_time 컬럼 + status ENUM 확장 - 4소스 UNION 대시보드: 방문(외부/내부) + TBM + 협력업체 통합 조회 - 체크인/체크아웃 API + 내부 출입 신고(승인 불필요) 지원 - 통합 출입 현황판 페이지 신규 (entry-dashboard.html) - 출입 신청/관리 페이지에 유형 필터 + 체크인/아웃 버튼 추가 - safety_entry_dashboard 권한 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,18 +6,31 @@ exports.createVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const requester_id = req.user.user_id;
|
||||
const requestData = { requester_id, ...req.body };
|
||||
const isInternal = requestData.request_type === 'internal';
|
||||
|
||||
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}는 필수 입력 항목입니다.` });
|
||||
// 내부 출입: visitor_name 필수, 외부: visitor_company 필수
|
||||
if (isInternal) {
|
||||
const requiredFields = ['visitor_name', '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}는 필수 입력 항목입니다.` });
|
||||
}
|
||||
}
|
||||
requestData.visitor_count = 1;
|
||||
requestData.visitor_company = requestData.visitor_company || '내부';
|
||||
} else {
|
||||
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: '출입 신청이 성공적으로 생성되었습니다.',
|
||||
message: isInternal ? '내부 출입 신고가 완료되었습니다.' : '출입 신청이 성공적으로 생성되었습니다.',
|
||||
data: { request_id: requestId }
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -34,7 +47,8 @@ exports.getAllVisitRequests = async (req, res) => {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
requester_id: req.query.requester_id,
|
||||
category_id: req.query.category_id
|
||||
category_id: req.query.category_id,
|
||||
request_type: req.query.request_type
|
||||
};
|
||||
|
||||
const requests = await visitRequestModel.getAllVisitRequests(filters);
|
||||
@@ -303,3 +317,67 @@ exports.getWorkplaces = async (req, res) => {
|
||||
res.status(500).json({ success: false, message: '작업장 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 체크인/체크아웃 ====================
|
||||
|
||||
exports.checkIn = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.checkIn(req.params.id, req.user.user_id);
|
||||
if (result.error) {
|
||||
return res.status(result.status).json({ success: false, message: result.error });
|
||||
}
|
||||
res.json({ success: true, message: '체크인되었습니다.' });
|
||||
} catch (err) {
|
||||
console.error('체크인 오류:', err);
|
||||
res.status(500).json({ success: false, message: '체크인 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.checkOut = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.checkOut(req.params.id, req.user.user_id);
|
||||
if (result.error) {
|
||||
return res.status(result.status).json({ success: false, message: result.error });
|
||||
}
|
||||
res.json({ success: true, message: '체크아웃되었습니다.' });
|
||||
} catch (err) {
|
||||
console.error('체크아웃 오류:', err);
|
||||
res.status(500).json({ success: false, message: '체크아웃 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 통합 대시보드 ====================
|
||||
|
||||
exports.getEntryDashboard = async (req, res) => {
|
||||
try {
|
||||
const date = req.query.date || new Date().toISOString().substring(0, 10);
|
||||
const data = await visitRequestModel.getEntryDashboard(date);
|
||||
res.json({ success: true, data, date });
|
||||
} catch (err) {
|
||||
console.error('출입 대시보드 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 대시보드 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.getEntryStats = async (req, res) => {
|
||||
try {
|
||||
const date = req.query.date || new Date().toISOString().substring(0, 10);
|
||||
const stats = await visitRequestModel.getEntryStats(date);
|
||||
res.json({ success: true, data: stats, date });
|
||||
} catch (err) {
|
||||
console.error('출입 통계 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 통계 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 부서 목록 ====================
|
||||
|
||||
exports.getDepartments = async (req, res) => {
|
||||
try {
|
||||
const departments = await visitRequestModel.getAllDepartments();
|
||||
res.json({ success: true, data: departments });
|
||||
} catch (err) {
|
||||
console.error('부서 목록 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '부서 목록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user