diff --git a/system1-factory/api/models/vacationBalanceModel.js b/system1-factory/api/models/vacationBalanceModel.js index 78c0f4f..7748e4f 100644 --- a/system1-factory/api/models/vacationBalanceModel.js +++ b/system1-factory/api/models/vacationBalanceModel.js @@ -105,7 +105,7 @@ const vacationBalanceModel = { async deductDays(userId, vacationTypeId, year, daysToDeduct) { const db = await getDb(); const [result] = await db.query(` - UPDATE vacation_balance_details + UPDATE sp_vacation_balances SET used_days = used_days + ?, updated_at = NOW() WHERE user_id = ? @@ -121,7 +121,7 @@ const vacationBalanceModel = { async restoreDays(userId, vacationTypeId, year, daysToRestore) { const db = await getDb(); const [result] = await db.query(` - UPDATE vacation_balance_details + UPDATE sp_vacation_balances SET used_days = GREATEST(0, used_days - ?), updated_at = NOW() WHERE user_id = ? @@ -138,20 +138,21 @@ const vacationBalanceModel = { const db = await getDb(); const [rows] = await db.query(` SELECT - vbd.id, - vbd.vacation_type_id, + svb.id, + svb.vacation_type_id, vt.type_name, vt.type_code, vt.priority, - vbd.total_days, - vbd.used_days, - vbd.remaining_days - FROM vacation_balance_details vbd - INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.user_id = ? - AND vbd.year = ? - AND vbd.remaining_days > 0 - ORDER BY vt.priority ASC + svb.total_days, + svb.used_days, + (svb.total_days - svb.used_days) AS remaining_days, + svb.balance_type + FROM sp_vacation_balances svb + INNER JOIN vacation_types vt ON svb.vacation_type_id = vt.id + WHERE svb.user_id = ? + AND svb.year = ? + AND (svb.total_days - svb.used_days) > 0 + ORDER BY vt.priority ASC, FIELD(svb.balance_type, 'CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT') `, [userId, year]); return rows; }, @@ -208,52 +209,56 @@ const vacationBalanceModel = { */ async deductByPriority(userId, year, daysToDeduct) { const db = await getDb(); + const conn = await db.getConnection(); + try { + await conn.beginTransaction(); - const [balances] = await db.query(` - SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days, - (vbd.total_days - vbd.used_days) as remaining_days, - vt.type_code, vt.type_name, vt.priority - FROM vacation_balance_details vbd - INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.user_id = ? AND vbd.year = ? - AND (vbd.total_days - vbd.used_days) > 0 - ORDER BY vt.priority ASC - `, [userId, year]); + const [balances] = await conn.query(` + SELECT svb.id, svb.vacation_type_id, svb.total_days, svb.used_days, + (svb.total_days - svb.used_days) AS remaining_days, + svb.balance_type, + vt.type_code, vt.type_name, vt.priority + FROM sp_vacation_balances svb + INNER JOIN vacation_types vt ON svb.vacation_type_id = vt.id + WHERE svb.user_id = ? AND svb.year = ? + AND (svb.total_days - svb.used_days) > 0 + ORDER BY vt.priority ASC, FIELD(svb.balance_type, 'CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT') + FOR UPDATE + `, [userId, year]); - if (balances.length === 0) { - console.warn(`[VacationBalance] 작업자 ${userId}의 ${year}년 휴가 잔액이 없습니다`); - return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 }; - } - - let remaining = daysToDeduct; - const deductions = []; - - for (const balance of balances) { - if (remaining <= 0) break; - - const available = parseFloat(balance.remaining_days); - const toDeduct = Math.min(remaining, available); - - if (toDeduct > 0) { - await db.query(` - UPDATE vacation_balance_details - SET used_days = used_days + ?, updated_at = NOW() - WHERE id = ? - `, [toDeduct, balance.id]); - - deductions.push({ - balance_id: balance.id, - type_code: balance.type_code, - type_name: balance.type_name, - deducted: toDeduct - }); - - remaining -= toDeduct; + if (balances.length === 0) { + await conn.rollback(); + console.warn(`[VacationBalance] 작업자 ${userId}의 ${year}년 휴가 잔액이 없습니다`); + return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 }; } - } - console.log(`[VacationBalance] 작업자 ${userId}: ${daysToDeduct}일 차감 완료`, deductions); - return { success: true, deductions, totalDeducted: daysToDeduct - remaining }; + let remaining = daysToDeduct; + const deductions = []; + + for (const balance of balances) { + if (remaining <= 0) break; + const available = parseFloat(balance.remaining_days); + const toDeduct = Math.min(remaining, available); + if (toDeduct > 0) { + await conn.query(` + UPDATE sp_vacation_balances + SET used_days = used_days + ?, updated_at = NOW() + WHERE id = ? + `, [toDeduct, balance.id]); + deductions.push({ balance_id: balance.id, type_code: balance.type_code, type_name: balance.type_name, balance_type: balance.balance_type, deducted: toDeduct }); + remaining -= toDeduct; + } + } + + await conn.commit(); + console.log(`[VacationBalance] 작업자 ${userId}: ${daysToDeduct}일 차감 완료`, deductions); + return { success: true, deductions, totalDeducted: daysToDeduct - remaining }; + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } }, /** @@ -261,46 +266,49 @@ const vacationBalanceModel = { */ async restoreByPriority(userId, year, daysToRestore) { const db = await getDb(); + const conn = await db.getConnection(); + try { + await conn.beginTransaction(); - const [balances] = await db.query(` - SELECT vbd.id, vbd.vacation_type_id, vbd.used_days, - vt.type_code, vt.type_name, vt.priority - FROM vacation_balance_details vbd - INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.user_id = ? AND vbd.year = ? - AND vbd.used_days > 0 - ORDER BY vt.priority DESC - `, [userId, year]); + const [balances] = await conn.query(` + SELECT svb.id, svb.vacation_type_id, svb.used_days, + svb.balance_type, + vt.type_code, vt.type_name, vt.priority + FROM sp_vacation_balances svb + INNER JOIN vacation_types vt ON svb.vacation_type_id = vt.id + WHERE svb.user_id = ? AND svb.year = ? + AND svb.used_days > 0 + ORDER BY vt.priority DESC, FIELD(svb.balance_type, 'COMPANY_GRANT', 'LONG_SERVICE', 'MANUAL', 'AUTO', 'CARRY_OVER') + FOR UPDATE + `, [userId, year]); - let remaining = daysToRestore; - const restorations = []; + let remaining = daysToRestore; + const restorations = []; - for (const balance of balances) { - if (remaining <= 0) break; - - const usedDays = parseFloat(balance.used_days); - const toRestore = Math.min(remaining, usedDays); - - if (toRestore > 0) { - await db.query(` - UPDATE vacation_balance_details - SET used_days = used_days - ?, updated_at = NOW() - WHERE id = ? - `, [toRestore, balance.id]); - - restorations.push({ - balance_id: balance.id, - type_code: balance.type_code, - type_name: balance.type_name, - restored: toRestore - }); - - remaining -= toRestore; + for (const balance of balances) { + if (remaining <= 0) break; + const usedDays = parseFloat(balance.used_days); + const toRestore = Math.min(remaining, usedDays); + if (toRestore > 0) { + await conn.query(` + UPDATE sp_vacation_balances + SET used_days = used_days - ?, updated_at = NOW() + WHERE id = ? + `, [toRestore, balance.id]); + restorations.push({ balance_id: balance.id, type_code: balance.type_code, type_name: balance.type_name, balance_type: balance.balance_type, restored: toRestore }); + remaining -= toRestore; + } } - } - console.log(`[VacationBalance] 작업자 ${userId}: ${daysToRestore}일 복구 완료`, restorations); - return { success: true, restorations, totalRestored: daysToRestore - remaining }; + await conn.commit(); + console.log(`[VacationBalance] 작업자 ${userId}: ${daysToRestore}일 복구 완료`, restorations); + return { success: true, restorations, totalRestored: daysToRestore - remaining }; + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } }, /**