/** * vacationBalanceController.js * 휴가 잔액 관련 컨트롤러 */ const vacationBalanceModel = require('../models/vacationBalanceModel'); const vacationTypeModel = require('../models/vacationTypeModel'); const logger = require('../utils/logger'); const vacationBalanceController = { /** * 특정 작업자의 휴가 잔액 조회 (특정 연도) * GET /api/vacation-balances/user/:userId/year/:year */ async getByWorkerAndYear(req, res) { try { const { userId, year } = req.params; const results = await vacationBalanceModel.getByWorkerAndYear(userId, year); res.json({ success: true, data: results }); } catch (error) { logger.error('휴가 잔액 조회 오류:', error); res.status(500).json({ success: false, message: '휴가 잔액을 조회하는 중 오류가 발생했습니다' }); } }, /** * 모든 작업자의 휴가 잔액 조회 (특정 연도) * GET /api/vacation-balances/year/:year */ async getAllByYear(req, res) { try { const { year } = req.params; const results = await vacationBalanceModel.getAllByYear(year); res.json({ success: true, data: results }); } catch (error) { logger.error('전체 휴가 잔액 조회 오류:', error); res.status(500).json({ success: false, message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다' }); } }, /** * 휴가 잔액 생성 * POST /api/vacation-balances */ async createBalance(req, res) { try { const { user_id, vacation_type_id, year, total_days, used_days, notes } = req.body; const created_by = req.user.user_id; if (!user_id || !vacation_type_id || !year || total_days === undefined) { return res.status(400).json({ success: false, message: '필수 필드가 누락되었습니다 (user_id, vacation_type_id, year, total_days)' }); } // 중복 체크 const existing = await vacationBalanceModel.getByWorkerTypeYear(user_id, vacation_type_id, year); if (existing && existing.length > 0) { return res.status(400).json({ success: false, message: '이미 해당 작업자의 해당 연도 휴가 잔액이 존재합니다' }); } const balanceData = { user_id, vacation_type_id, year, total_days, used_days: used_days || 0, notes: notes || null, created_by }; const result = await vacationBalanceModel.create(balanceData); res.status(201).json({ success: true, message: '휴가 잔액이 생성되었습니다', data: { id: result.insertId } }); } catch (error) { logger.error('휴가 잔액 생성 오류:', error); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' }); } }, /** * 휴가 잔액 수정 * PUT /api/vacation-balances/:id */ async updateBalance(req, res) { try { const { id } = req.params; const { total_days, used_days, notes } = req.body; const updateData = {}; if (total_days !== undefined) updateData.total_days = total_days; if (used_days !== undefined) updateData.used_days = used_days; if (notes !== undefined) updateData.notes = notes; updateData.updated_at = new Date(); if (Object.keys(updateData).length === 1) { return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' }); } const result = await vacationBalanceModel.update(id, updateData); if (result.affectedRows === 0) { return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' }); } res.json({ success: true, message: '휴가 잔액이 수정되었습니다' }); } catch (error) { logger.error('휴가 잔액 수정 오류:', error); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' }); } }, /** * 휴가 잔액 삭제 * DELETE /api/vacation-balances/:id */ async deleteBalance(req, res) { try { const { id } = req.params; const result = await vacationBalanceModel.delete(id); if (result.affectedRows === 0) { return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' }); } res.json({ success: true, message: '휴가 잔액이 삭제되었습니다' }); } catch (error) { logger.error('휴가 잔액 삭제 오류:', error); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' }); } }, /** * 근속년수 기반 연차 자동 계산 및 생성 * POST /api/vacation-balances/auto-calculate */ async autoCalculateAndCreate(req, res) { try { const { user_id, hire_date, year } = req.body; const created_by = req.user.user_id; if (!user_id || !hire_date || !year) { return res.status(400).json({ success: false, message: '필수 필드가 누락되었습니다 (user_id, hire_date, year)' }); } const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year); // ANNUAL 휴가 유형 ID 조회 const types = await vacationTypeModel.getByCode('ANNUAL'); if (!types || types.length === 0) { return res.status(500).json({ success: false, message: 'ANNUAL 휴가 유형을 찾을 수 없습니다' }); } const annualTypeId = types[0].id; // 중복 체크 const existing = await vacationBalanceModel.getByWorkerTypeYear(user_id, annualTypeId, year); if (existing && existing.length > 0) { return res.status(400).json({ success: false, message: '이미 해당 작업자의 해당 연도 연차가 존재합니다' }); } const balanceData = { user_id, vacation_type_id: annualTypeId, year, total_days: annualDays, used_days: 0, notes: `근속년수 기반 자동 계산 (입사일: ${hire_date})`, created_by }; const result = await vacationBalanceModel.create(balanceData); res.status(201).json({ success: true, message: `${annualDays}일의 연차가 자동으로 생성되었습니다`, data: { id: result.insertId, calculated_days: annualDays } }); } catch (error) { logger.error('연차 자동 계산 오류:', error); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' }); } }, /** * 휴가 잔액 일괄 저장 (upsert) * POST /api/vacation-balances/bulk-upsert */ async bulkUpsert(req, res) { try { const { balances } = req.body; const created_by = req.user.user_id; if (!balances || !Array.isArray(balances) || balances.length === 0) { return res.status(400).json({ success: false, message: '저장할 데이터가 없습니다' }); } const { getDb } = require('../dbPool'); const db = await getDb(); let successCount = 0; let errorCount = 0; for (const balance of balances) { const { user_id, vacation_type_id, year, total_days, notes, balance_type } = balance; if (!user_id || !vacation_type_id || !year || total_days === undefined) { errorCount++; continue; } try { const btype = balance_type || 'AUTO'; const query = ` INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at) VALUES (?, ?, ?, ?, 0, ?, ?, ?, NULL) ON DUPLICATE KEY UPDATE total_days = VALUES(total_days), notes = VALUES(notes) `; await db.query(query, [user_id, vacation_type_id, year, total_days, notes || null, created_by, btype]); successCount++; } catch (err) { logger.error('휴가 잔액 저장 오류:', err); errorCount++; } } res.json({ success: true, message: `${successCount}건 저장 완료${errorCount > 0 ? `, ${errorCount}건 실패` : ''}`, data: { successCount, errorCount } }); } catch (error) { logger.error('bulkUpsert 오류:', error); res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' }); } }, /** * 작업자의 사용 가능한 휴가 일수 조회 * GET /api/vacation-balances/user/:userId/year/:year/available */ async getAvailableDays(req, res) { try { const { userId, year } = req.params; const results = await vacationBalanceModel.getAvailableVacationDays(userId, year); res.json({ success: true, data: results }); } catch (error) { logger.error('사용 가능 휴가 조회 오류:', error); res.status(500).json({ success: false, message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' }); } } }; module.exports = vacationBalanceController;