feat(vacation): 이월연차 만료 시스템 + 대시보드 합산 개선
- createBalance/bulkUpsert에서 CARRY_OVER expires_at 자동 설정 (carry_over_expiry_month 설정 기반, 기본값 2월말) - deductByPriority/deductDays 쿼리에 만료 필터 추가 (expires_at >= CURDATE() 조건, 만료된 이월 차감 제외) - 대시보드 합산에서 만료된 잔액 제외 - DB 보정 완료: CARRY_OVER 8건 expires_at 설정, 황인용/최광욱 소급 재계산 (이월 우선 차감 반영) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,9 +44,11 @@ const DashboardController = {
|
||||
remaining: parseFloat(v.remaining_days) || 0
|
||||
}));
|
||||
|
||||
// 모든 balance_type 합산
|
||||
const totalDays = vacationRows.reduce((s, v) => s + (parseFloat(v.total_days) || 0), 0);
|
||||
const usedDays = vacationRows.reduce((s, v) => s + (parseFloat(v.used_days) || 0), 0);
|
||||
// 만료되지 않은 balance만 합산 (만료된 이월연차 제외)
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
const activeRows = vacationRows.filter(v => !v.expires_at || v.expires_at >= today);
|
||||
const totalDays = activeRows.reduce((s, v) => s + (parseFloat(v.total_days) || 0), 0);
|
||||
const usedDays = activeRows.reduce((s, v) => s + (parseFloat(v.used_days) || 0), 0);
|
||||
const remainingDays = totalDays - usedDays;
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -223,6 +223,7 @@ const vacationBalanceModel = {
|
||||
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
|
||||
AND (svb.expires_at IS NULL OR svb.expires_at >= CURDATE())
|
||||
ORDER BY vt.priority ASC, FIELD(svb.balance_type, 'CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT')
|
||||
FOR UPDATE
|
||||
`, [userId, year]);
|
||||
|
||||
@@ -97,7 +97,8 @@ const vacationBalanceModel = {
|
||||
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 ${excludeClause}
|
||||
WHERE user_id = ? AND year = ? AND (total_days - used_days) > 0
|
||||
AND (expires_at IS NULL OR expires_at >= CURDATE()) ${excludeClause}
|
||||
ORDER BY FIELD(balance_type, 'CARRY_OVER', 'AUTO', 'MANUAL', 'LONG_SERVICE', 'COMPANY_GRANT')
|
||||
FOR UPDATE
|
||||
`, queryParams);
|
||||
|
||||
@@ -131,6 +131,14 @@ async function getBalanceById(id) {
|
||||
|
||||
async function createBalance({ user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at }) {
|
||||
const db = getPool();
|
||||
// CARRY_OVER일 때 expires_at 미설정이면 자동 계산
|
||||
if (balance_type === 'CARRY_OVER' && !expires_at) {
|
||||
const vacationSettingsModel = require('./vacationSettingsModel');
|
||||
const settings = await vacationSettingsModel.loadAsObject();
|
||||
const expiryMonth = parseInt(settings.carry_over_expiry_month) || 2;
|
||||
const lastDay = new Date(year, expiryMonth, 0); // 해당 월 말일
|
||||
expires_at = lastDay.toISOString().substring(0, 10);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -160,13 +168,21 @@ async function deleteBalance(id) {
|
||||
|
||||
async function bulkUpsertBalances(balances) {
|
||||
const db = getPool();
|
||||
const vacationSettingsModel = require('./vacationSettingsModel');
|
||||
const settings = await vacationSettingsModel.loadAsObject();
|
||||
const expiryMonth = parseInt(settings.carry_over_expiry_month) || 2;
|
||||
let count = 0;
|
||||
for (const b of balances) {
|
||||
let expiresAt = b.expires_at || null;
|
||||
if (b.balance_type === 'CARRY_OVER' && !expiresAt) {
|
||||
const lastDay = new Date(b.year, expiryMonth, 0);
|
||||
expiresAt = lastDay.toISOString().substring(0, 10);
|
||||
}
|
||||
await db.query(
|
||||
`INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE total_days = VALUES(total_days), notes = VALUES(notes)`,
|
||||
[b.user_id, b.vacation_type_id, b.year, b.total_days ?? 0, b.used_days ?? 0, b.notes || null, b.created_by, b.balance_type || 'AUTO', b.expires_at || null]
|
||||
[b.user_id, b.vacation_type_id, b.year, b.total_days ?? 0, b.used_days ?? 0, b.notes || null, b.created_by, b.balance_type || 'AUTO', expiresAt]
|
||||
);
|
||||
count++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user