diff --git a/tksupport/api/models/vacationBalanceModel.js b/tksupport/api/models/vacationBalanceModel.js index 444aa82..f8063de 100644 --- a/tksupport/api/models/vacationBalanceModel.js +++ b/tksupport/api/models/vacationBalanceModel.js @@ -61,24 +61,76 @@ const vacationBalanceModel = { return result; }, + // 우선순위: 이월 → 기본연차 → 추가부여 → 장기근속 → 회사부여 async deductDays(userId, vacationTypeId, year, daysToDeduct, conn) { const db = conn || getPool(); - const [result] = await db.query(` - UPDATE sp_vacation_balances - SET used_days = used_days + ?, updated_at = NOW() - WHERE user_id = ? AND vacation_type_id = ? AND year = ? - `, [daysToDeduct, userId, vacationTypeId, year]); - return result; + const needRelease = !conn; + const c = needRelease ? await db.getConnection() : db; + try { + if (needRelease) await c.beginTransaction(); + + const [balances] = await c.query(` + SELECT id, total_days, used_days, (total_days - used_days) AS remaining_days, balance_type + FROM sp_vacation_balances + WHERE user_id = ? AND year = ? AND (total_days - used_days) > 0 + ORDER BY FIELD(balance_type, 'CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT') + FOR UPDATE + `, [userId, year]); + + let remaining = daysToDeduct; + for (const b of balances) { + if (remaining <= 0) break; + const toDeduct = Math.min(remaining, parseFloat(b.remaining_days)); + if (toDeduct > 0) { + await c.query('UPDATE sp_vacation_balances SET used_days = used_days + ?, updated_at = NOW() WHERE id = ?', [toDeduct, b.id]); + remaining -= toDeduct; + } + } + + if (needRelease) await c.commit(); + return { affectedRows: balances.length }; + } catch (err) { + if (needRelease) await c.rollback(); + throw err; + } finally { + if (needRelease) c.release(); + } }, + // 복원: 역순 (회사부여 → 장기근속 → 추가부여 → 기본연차 → 이월) async restoreDays(userId, vacationTypeId, year, daysToRestore, conn) { const db = conn || getPool(); - const [result] = await db.query(` - UPDATE sp_vacation_balances - SET used_days = GREATEST(0, used_days - ?), updated_at = NOW() - WHERE user_id = ? AND vacation_type_id = ? AND year = ? - `, [daysToRestore, userId, vacationTypeId, year]); - return result; + const needRelease = !conn; + const c = needRelease ? await db.getConnection() : db; + try { + if (needRelease) await c.beginTransaction(); + + const [balances] = await c.query(` + SELECT id, used_days, balance_type + FROM sp_vacation_balances + WHERE user_id = ? AND year = ? AND used_days > 0 + ORDER BY FIELD(balance_type, 'COMPANY_GRANT', 'LONG_SERVICE', 'MANUAL', 'AUTO', 'CARRY_OVER') + FOR UPDATE + `, [userId, year]); + + let remaining = daysToRestore; + for (const b of balances) { + if (remaining <= 0) break; + const toRestore = Math.min(remaining, parseFloat(b.used_days)); + if (toRestore > 0) { + await c.query('UPDATE sp_vacation_balances SET used_days = used_days - ?, updated_at = NOW() WHERE id = ?', [toRestore, b.id]); + remaining -= toRestore; + } + } + + if (needRelease) await c.commit(); + return { affectedRows: balances.length }; + } catch (err) { + if (needRelease) await c.rollback(); + throw err; + } finally { + if (needRelease) c.release(); + } }, calculateAnnualLeaveDays(hireDate, targetYear) {