const vacationRequestModel = require('../models/vacationRequestModel'); const vacationBalanceModel = require('../models/vacationBalanceModel'); const { getPool } = require('../middleware/auth'); const vacationController = { // ─── 휴가 신청 ─── async createRequest(req, res) { try { const { vacation_type_id, start_date, end_date, days_used, reason } = req.body; const user_id = req.user.user_id || req.user.id; if (!vacation_type_id || !start_date || !end_date || !days_used) { return res.status(400).json({ success: false, error: '필수 필드가 누락되었습니다' }); } if (new Date(end_date) < new Date(start_date)) { return res.status(400).json({ success: false, error: '종료일은 시작일보다 이후여야 합니다' }); } const overlapRows = await vacationRequestModel.checkOverlap(user_id, start_date, end_date); if (overlapRows[0].count > 0) { return res.status(400).json({ success: false, error: '해당 기간에 이미 신청된 휴가가 있습니다' }); } const result = await vacationRequestModel.create({ user_id, vacation_type_id, start_date, end_date, days_used, reason: reason || null, status: 'pending' }); res.status(201).json({ success: true, message: '휴가 신청이 완료되었습니다', data: { request_id: result.insertId } }); } catch (error) { console.error('휴가 신청 생성 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async getRequests(req, res) { try { const role = (req.user.role || '').toLowerCase(); const userId = req.user.user_id || req.user.id; const filters = { 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 (!['admin', 'system'].includes(role)) { filters.user_id = userId; } else if (req.query.user_id) { filters.user_id = req.query.user_id; } const results = await vacationRequestModel.getAll(filters); res.json({ success: true, data: results }); } catch (error) { console.error('휴가 신청 목록 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async getRequestById(req, res) { try { const results = await vacationRequestModel.getById(req.params.id); if (results.length === 0) { return res.status(404).json({ success: false, error: '해당 휴가 신청을 찾을 수 없습니다' }); } const request = results[0]; const role = (req.user.role || '').toLowerCase(); const userId = req.user.user_id || req.user.id; if (!['admin', 'system'].includes(role) && userId !== request.user_id) { return res.status(403).json({ success: false, error: '권한이 없습니다' }); } res.json({ success: true, data: request }); } catch (error) { console.error('휴가 신청 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async updateRequest(req, res) { try { const { id } = req.params; const { start_date, end_date, days_used, reason, vacation_type_id } = req.body; const results = await vacationRequestModel.getById(id); if (results.length === 0) { return res.status(404).json({ success: false, error: '해당 휴가 신청을 찾을 수 없습니다' }); } const existing = results[0]; const role = (req.user.role || '').toLowerCase(); const userId = req.user.user_id || req.user.id; if (!['admin', 'system'].includes(role) && userId !== existing.user_id) { return res.status(403).json({ success: false, error: '권한이 없습니다' }); } if (existing.status !== 'pending') { return res.status(400).json({ success: false, error: '대기 중인 신청만 수정할 수 있습니다' }); } const updateData = {}; if (vacation_type_id) updateData.vacation_type_id = vacation_type_id; 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 newStart = start_date || existing.start_date; const newEnd = end_date || existing.end_date; const overlapRows = await vacationRequestModel.checkOverlap(existing.user_id, newStart, newEnd, id); if (overlapRows[0].count > 0) { return res.status(400).json({ success: false, error: '해당 기간에 이미 신청된 휴가가 있습니다' }); } } await vacationRequestModel.update(id, updateData); res.json({ success: true, message: '휴가 신청이 수정되었습니다' }); } catch (error) { console.error('휴가 신청 수정 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async cancelRequest(req, res) { try { const { id } = req.params; const results = await vacationRequestModel.getById(id); if (results.length === 0) { return res.status(404).json({ success: false, error: '해당 휴가 신청을 찾을 수 없습니다' }); } const existing = results[0]; const role = (req.user.role || '').toLowerCase(); const userId = req.user.user_id || req.user.id; if (!['admin', 'system'].includes(role) && userId !== existing.user_id) { return res.status(403).json({ success: false, error: '권한이 없습니다' }); } if (existing.status === 'cancelled') { return res.status(400).json({ success: false, error: '이미 취소된 신청입니다' }); } // 승인된 건 취소 시 잔여일 복구 if (existing.status === 'approved') { const year = new Date(existing.start_date).getFullYear(); await vacationBalanceModel.restoreDays( existing.user_id, existing.vacation_type_id, year, parseFloat(existing.days_used) ); } await vacationRequestModel.updateStatus(id, { status: 'cancelled', reviewed_by: userId, review_note: '취소됨' }); res.json({ success: true, message: '휴가 신청이 취소되었습니다' }); } catch (error) { console.error('휴가 취소 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, // ─── 승인/반려 (관리자) ─── async getPending(req, res) { try { const results = await vacationRequestModel.getAllPending(); res.json({ success: true, data: results }); } catch (error) { console.error('대기 목록 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async approveRequest(req, res) { try { const { id } = req.params; const { review_note } = req.body; const reviewed_by = req.user.user_id || req.user.id; const results = await vacationRequestModel.getById(id); if (results.length === 0) { return res.status(404).json({ success: false, error: '해당 휴가 신청을 찾을 수 없습니다' }); } if (results[0].status !== 'pending') { return res.status(400).json({ success: false, error: '이미 처리된 신청입니다' }); } const request = results[0]; // 잔여일 차감 const year = new Date(request.start_date).getFullYear(); await vacationBalanceModel.deductDays( request.user_id, request.vacation_type_id, year, parseFloat(request.days_used) ); await vacationRequestModel.updateStatus(id, { status: 'approved', reviewed_by, review_note }); res.json({ success: true, message: '휴가 신청이 승인되었습니다' }); } catch (error) { console.error('휴가 승인 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async rejectRequest(req, res) { try { const { id } = req.params; const { review_note } = req.body; const reviewed_by = req.user.user_id || req.user.id; const results = await vacationRequestModel.getById(id); if (results.length === 0) { return res.status(404).json({ success: false, error: '해당 휴가 신청을 찾을 수 없습니다' }); } if (results[0].status !== 'pending') { return res.status(400).json({ success: false, error: '이미 처리된 신청입니다' }); } await vacationRequestModel.updateStatus(id, { status: 'rejected', reviewed_by, review_note }); res.json({ success: true, message: '휴가 신청이 반려되었습니다' }); } catch (error) { console.error('휴가 반려 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, // ─── 잔여일 ─── async getMyBalance(req, res) { try { const userId = req.user.user_id || req.user.id; const year = parseInt(req.query.year) || new Date().getFullYear(); const balances = await vacationBalanceModel.getByUserAndYear(userId, year); const hireDate = await vacationBalanceModel.getUserHireDate(userId); res.json({ success: true, data: { balances, hire_date: hireDate } }); } catch (error) { console.error('잔여일 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async getUserBalance(req, res) { try { const { userId } = req.params; const year = parseInt(req.query.year) || new Date().getFullYear(); const balances = await vacationBalanceModel.getByUserAndYear(userId, year); const hireDate = await vacationBalanceModel.getUserHireDate(userId); res.json({ success: true, data: { balances, hire_date: hireDate } }); } catch (error) { console.error('사용자 잔여일 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async allocateBalance(req, res) { try { const { user_id, vacation_type_id, year, total_days, notes } = req.body; const created_by = req.user.user_id || req.user.id; if (!user_id || !vacation_type_id || !year || total_days === undefined) { return res.status(400).json({ success: false, error: '필수 필드가 누락되었습니다' }); } await vacationBalanceModel.allocate({ user_id, vacation_type_id, year, total_days, notes, created_by }); res.json({ success: true, message: '휴가 잔여일이 배정되었습니다' }); } catch (error) { console.error('잔여일 배정 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, async getAllBalances(req, res) { try { const year = parseInt(req.query.year) || new Date().getFullYear(); const balances = await vacationBalanceModel.getAllByYear(year); res.json({ success: true, data: balances }); } catch (error) { console.error('전체 잔여일 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, // ─── 참조 데이터 ─── async getVacationTypes(req, res) { try { const db = getPool(); const [rows] = await db.query('SELECT * FROM vacation_types ORDER BY priority ASC, type_name ASC'); res.json({ success: true, data: rows }); } catch (error) { console.error('휴가 유형 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } }, // ─── 사용자 목록 (관리자 - 배정용) ─── async getUsers(req, res) { try { const db = getPool(); const [rows] = await db.query(` SELECT user_id, username, name, hire_date FROM sso_users WHERE is_active = 1 ORDER BY name ASC `); res.json({ success: true, data: rows }); } catch (error) { console.error('사용자 목록 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } } }; module.exports = vacationController;