- 실시간 작업장 현황을 지도로 시각화 - 작업장 관리 페이지에서 정의한 구역 정보 활용 - TBM 작업자 및 방문자 현황 표시 주요 변경사항: - dashboard.html: 작업장 현황 섹션 추가 (기존 작업 현황 테이블 제거) - workplace-status.js: 지도 렌더링 및 데이터 통합 로직 구현 - modern-dashboard.js: 삭제된 DOM 요소 조건부 체크 추가 시각화 방식: - 인원 없음: 회색 테두리 + 작업장 이름 - 내부 작업자: 파란색 영역 + 인원 수 - 외부 방문자: 보라색 영역 + 인원 수 - 둘 다: 초록색 영역 + 총 인원 수 기술 구현: - Canvas API 기반 사각형 영역 렌더링 - map-regions API를 통한 데이터 일관성 보장 - 클릭 이벤트로 상세 정보 모달 표시 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
566 lines
16 KiB
JavaScript
566 lines
16 KiB
JavaScript
/**
|
|
* vacationRequestController.js
|
|
* 휴가 신청 관련 컨트롤러
|
|
*/
|
|
|
|
const vacationRequestModel = require('../models/vacationRequestModel');
|
|
// TODO: workerVacationBalanceModel 구현 필요
|
|
// const workerVacationBalanceModel = require('../models/workerVacationBalanceModel');
|
|
|
|
const vacationRequestController = {
|
|
/**
|
|
* 휴가 신청 생성
|
|
*/
|
|
async createRequest(req, res) {
|
|
try {
|
|
const { worker_id, vacation_type_id, start_date, end_date, days_used, reason } = req.body;
|
|
const requested_by = req.user.user_id;
|
|
|
|
// 필수 필드 검증
|
|
if (!worker_id || !vacation_type_id || !start_date || !end_date || !days_used) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '필수 필드가 누락되었습니다'
|
|
});
|
|
}
|
|
|
|
// 날짜 유효성 검증
|
|
const startDate = new Date(start_date);
|
|
const endDate = new Date(end_date);
|
|
|
|
if (endDate < startDate) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '종료일은 시작일보다 이후여야 합니다'
|
|
});
|
|
}
|
|
|
|
// 기간 중복 체크
|
|
vacationRequestModel.checkOverlap(worker_id, start_date, end_date, null, (err, results) => {
|
|
if (err) {
|
|
console.error('기간 중복 체크 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '기간 중복 체크 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results[0].count > 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '해당 기간에 이미 신청된 휴가가 있습니다'
|
|
});
|
|
}
|
|
|
|
// TODO: 잔여 연차 확인 로직 구현 필요
|
|
// 현재는 잔여 연차 확인 없이 신청 가능
|
|
|
|
// 휴가 신청 생성
|
|
const requestData = {
|
|
worker_id,
|
|
vacation_type_id,
|
|
start_date,
|
|
end_date,
|
|
days_used,
|
|
reason: reason || null,
|
|
status: 'pending',
|
|
requested_by
|
|
};
|
|
|
|
vacationRequestModel.create(requestData, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 신청 생성 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 생성 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: '휴가 신청이 완료되었습니다',
|
|
data: {
|
|
request_id: result.insertId
|
|
}
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 신청 생성 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 신청 목록 조회
|
|
*/
|
|
async getAllRequests(req, res) {
|
|
try {
|
|
const filters = {
|
|
worker_id: req.query.worker_id,
|
|
status: req.query.status,
|
|
start_date: req.query.start_date,
|
|
end_date: req.query.end_date,
|
|
vacation_type_id: req.query.vacation_type_id
|
|
};
|
|
|
|
// 일반 사용자는 자신의 신청만 조회 가능
|
|
if (req.user.access_level !== 'system') {
|
|
if (req.user.worker_id) {
|
|
filters.worker_id = req.user.worker_id;
|
|
} else {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '권한이 없습니다'
|
|
});
|
|
}
|
|
}
|
|
|
|
vacationRequestModel.getAll(filters, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 목록 조회 오류:', err);
|
|
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: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 특정 휴가 신청 조회
|
|
*/
|
|
async getRequestById(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
vacationRequestModel.getById(id, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 조회 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 조회 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '해당 휴가 신청을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const request = results[0];
|
|
|
|
// 권한 검증: 관리자 또는 본인만 조회 가능
|
|
if (req.user.access_level !== 'system' && req.user.worker_id !== request.worker_id) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '권한이 없습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: request
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 신청 조회 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 신청 수정 (대기 중인 신청만)
|
|
*/
|
|
async updateRequest(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { start_date, end_date, days_used, reason } = req.body;
|
|
|
|
// 기존 신청 조회
|
|
vacationRequestModel.getById(id, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 조회 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 조회 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '해당 휴가 신청을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const existingRequest = results[0];
|
|
|
|
// 권한 검증
|
|
if (req.user.access_level !== 'system' && req.user.worker_id !== existingRequest.worker_id) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '권한이 없습니다'
|
|
});
|
|
}
|
|
|
|
// 대기 중인 신청만 수정 가능
|
|
if (existingRequest.status !== 'pending') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '승인/거부된 신청은 수정할 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const updateData = {};
|
|
if (start_date) updateData.start_date = start_date;
|
|
if (end_date) updateData.end_date = end_date;
|
|
if (days_used) updateData.days_used = days_used;
|
|
if (reason !== undefined) updateData.reason = reason;
|
|
|
|
// 날짜가 변경된 경우 중복 체크
|
|
if (start_date || end_date) {
|
|
const newStartDate = start_date || existingRequest.start_date;
|
|
const newEndDate = end_date || existingRequest.end_date;
|
|
|
|
vacationRequestModel.checkOverlap(
|
|
existingRequest.worker_id,
|
|
newStartDate,
|
|
newEndDate,
|
|
id,
|
|
(err, overlapResults) => {
|
|
if (err) {
|
|
console.error('기간 중복 체크 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '기간 중복 체크 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (overlapResults[0].count > 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '해당 기간에 이미 신청된 휴가가 있습니다'
|
|
});
|
|
}
|
|
|
|
// 수정 실행
|
|
vacationRequestModel.update(id, updateData, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 신청 수정 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 수정 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 신청이 수정되었습니다'
|
|
});
|
|
});
|
|
}
|
|
);
|
|
} else {
|
|
// 날짜 변경 없이 바로 수정
|
|
vacationRequestModel.update(id, updateData, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 신청 수정 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 수정 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 신청이 수정되었습니다'
|
|
});
|
|
});
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 신청 수정 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 신청 삭제 (대기 중인 신청만)
|
|
*/
|
|
async deleteRequest(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// 기존 신청 조회
|
|
vacationRequestModel.getById(id, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 조회 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 조회 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '해당 휴가 신청을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const existingRequest = results[0];
|
|
|
|
// 권한 검증
|
|
if (req.user.access_level !== 'system' && req.user.worker_id !== existingRequest.worker_id) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '권한이 없습니다'
|
|
});
|
|
}
|
|
|
|
// 대기 중인 신청만 삭제 가능
|
|
if (existingRequest.status !== 'pending') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '승인/거부된 신청은 삭제할 수 없습니다'
|
|
});
|
|
}
|
|
|
|
vacationRequestModel.delete(id, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 신청 삭제 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 삭제 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 신청이 삭제되었습니다'
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 신청 삭제 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 신청 승인 (관리자만)
|
|
*/
|
|
async approveRequest(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { review_note } = req.body;
|
|
const reviewed_by = req.user.user_id;
|
|
|
|
// 관리자 권한 확인
|
|
if (req.user.access_level !== 'system') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '관리자만 승인할 수 있습니다'
|
|
});
|
|
}
|
|
|
|
// 기존 신청 조회
|
|
vacationRequestModel.getById(id, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 조회 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 조회 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '해당 휴가 신청을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const request = results[0];
|
|
|
|
if (request.status !== 'pending') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '이미 처리된 신청입니다'
|
|
});
|
|
}
|
|
|
|
// 상태 업데이트
|
|
const statusData = {
|
|
status: 'approved',
|
|
reviewed_by,
|
|
review_note
|
|
};
|
|
|
|
vacationRequestModel.updateStatus(id, statusData, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 승인 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 승인 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
// TODO: 잔여 연차에서 차감 로직 구현 필요
|
|
// 현재는 연차 차감 없이 승인만 처리
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 신청이 승인되었습니다'
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 승인 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 신청 거부 (관리자만)
|
|
*/
|
|
async rejectRequest(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { review_note } = req.body;
|
|
const reviewed_by = req.user.user_id;
|
|
|
|
// 관리자 권한 확인
|
|
if (req.user.access_level !== 'system') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '관리자만 거부할 수 있습니다'
|
|
});
|
|
}
|
|
|
|
// 기존 신청 조회
|
|
vacationRequestModel.getById(id, (err, results) => {
|
|
if (err) {
|
|
console.error('휴가 신청 조회 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 신청 조회 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '해당 휴가 신청을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
const request = results[0];
|
|
|
|
if (request.status !== 'pending') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '이미 처리된 신청입니다'
|
|
});
|
|
}
|
|
|
|
// 상태 업데이트
|
|
const statusData = {
|
|
status: 'rejected',
|
|
reviewed_by,
|
|
review_note
|
|
};
|
|
|
|
vacationRequestModel.updateStatus(id, statusData, (err, result) => {
|
|
if (err) {
|
|
console.error('휴가 거부 오류:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: '휴가 거부 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 신청이 거부되었습니다'
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('휴가 거부 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 대기 중인 휴가 신청 목록 (관리자용)
|
|
*/
|
|
async getPendingRequests(req, res) {
|
|
try {
|
|
// 관리자 권한 확인
|
|
if (req.user.access_level !== 'system') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: '관리자만 조회할 수 있습니다'
|
|
});
|
|
}
|
|
|
|
vacationRequestModel.getAllPending((err, results) => {
|
|
if (err) {
|
|
console.error('대기 중인 휴가 신청 조회 오류:', err);
|
|
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: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = vacationRequestController;
|