const { getPool } = require('../middleware/auth'); const companyHolidayModel = { async getByYear(year) { const db = getPool(); const [rows] = await db.query( 'SELECT * FROM company_holidays WHERE YEAR(holiday_date) = ? ORDER BY holiday_date', [year] ); return rows; }, async getById(id) { const db = getPool(); const [rows] = await db.query('SELECT * FROM company_holidays WHERE id = ?', [id]); return rows.length > 0 ? rows[0] : null; }, async create(data) { const db = getPool(); const [result] = await db.query( 'INSERT INTO company_holidays (holiday_date, holiday_name, holiday_type, description, created_by) VALUES (?, ?, ?, ?, ?)', [data.holiday_date, data.holiday_name, data.holiday_type, data.description || null, data.created_by] ); return result; }, async delete(id) { const db = getPool(); const [result] = await db.query('DELETE FROM company_holidays WHERE id = ?', [id]); return result; }, async applyAnnualDeduction(holidayId) { const db = getPool(); const conn = await db.getConnection(); try { await conn.beginTransaction(); // 1. SELECT FOR UPDATE — 동시 실행 방지 const [holidays] = await conn.query( 'SELECT * FROM company_holidays WHERE id = ? FOR UPDATE', [holidayId] ); if (holidays.length === 0) { throw new Error('해당 전사 휴가를 찾을 수 없습니다'); } const holiday = holidays[0]; if (holiday.holiday_type !== 'ANNUAL_DEDUCT') { throw new Error('연차차감 유형의 휴가만 차감할 수 있습니다'); } if (holiday.deduction_applied_at) { throw new Error('이미 차감이 실행된 휴가입니다'); } // 2. vacation_types에서 ANNUAL_FULL id 조회 const [types] = await conn.query( "SELECT id FROM vacation_types WHERE type_code = 'ANNUAL_FULL'" ); if (types.length === 0) { throw new Error('연차(ANNUAL_FULL) 유형이 존재하지 않습니다'); } const typeId = types[0].id; // 3. 활성 사원 연차 차감 (관리계정 제외, 입사일 이후 휴가만 적용) const [result] = await conn.query(` UPDATE sp_vacation_balances SET used_days = used_days + 1, updated_at = NOW() WHERE vacation_type_id = ? AND year = YEAR(?) AND balance_type = 'AUTO' AND user_id IN ( SELECT user_id FROM sso_users WHERE is_active = 1 AND hire_date IS NOT NULL AND hire_date <= ? ) `, [typeId, holiday.holiday_date, holiday.holiday_date]); // 4. 차감 완료 표시 await conn.query( 'UPDATE company_holidays SET deduction_applied_at = NOW() WHERE id = ?', [holidayId] ); // 5. balance 없는 사원 수 체크 (경고용, 관리계정·미입사자 제외) const [activeUsers] = await conn.query( 'SELECT COUNT(*) as cnt FROM sso_users WHERE is_active = 1 AND hire_date IS NOT NULL AND hire_date <= ?', [holiday.holiday_date] ); const missing = activeUsers[0].cnt - result.affectedRows; await conn.commit(); return { affected_count: result.affectedRows, missing_balance_count: missing }; } catch (err) { await conn.rollback(); throw err; } finally { conn.release(); } } }; module.exports = companyHolidayModel;