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