feat(tkfb): 연차 차감/복원을 sp_vacation_balances 정본으로 전환
- deductByPriority/restoreByPriority: vacation_balance_details → sp_vacation_balances - 트랜잭션 + SELECT FOR UPDATE 적용 (tksupport/tkuser 동시 쓰기 안전) - balance_type 우선순위: CARRY_OVER → AUTO → MANUAL → LONG_SERVICE → COMPANY_GRANT - deductDays/restoreDays/getAvailableVacationDays도 sp_vacation_balances로 전환 - DB: sp_vacation_balances DECIMAL(4,1) → DECIMAL(5,2) ALTER 완료 (반반차 0.25 지원) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user