feat(tksupport): 휴가 보정 관리 페이지 추가 — 캘린더 기반 추가/삭제
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -343,6 +343,80 @@ const vacationController = {
|
||||
console.error('사용자 목록 조회 오류:', error);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
// ─── 관리자 보정 ───
|
||||
|
||||
async adminCreateRequest(req, res) {
|
||||
const db = getPool();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
const { user_id, vacation_type_id, start_date, end_date, days_used, reason } = req.body;
|
||||
if (!user_id || !vacation_type_id || !start_date || !end_date || !days_used) {
|
||||
return res.status(400).json({ success: false, error: '필수 필드가 누락되었습니다' });
|
||||
}
|
||||
const daysVal = parseFloat(days_used);
|
||||
if (daysVal <= 0 || daysVal > 30) {
|
||||
return res.status(400).json({ success: false, error: '일수는 0 초과 30 이하여야 합니다' });
|
||||
}
|
||||
if (new Date(start_date).getFullYear() !== new Date(end_date).getFullYear()) {
|
||||
return res.status(400).json({ success: false, error: '연도를 걸친 휴가는 연도별로 분리하여 입력해주세요' });
|
||||
}
|
||||
|
||||
const adminId = req.user.user_id || req.user.id;
|
||||
const year = new Date(start_date).getFullYear();
|
||||
|
||||
await conn.beginTransaction();
|
||||
const result = await vacationRequestModel.create({
|
||||
user_id, vacation_type_id, start_date, end_date,
|
||||
days_used: daysVal, reason: reason || null,
|
||||
status: 'approved', reviewed_by: adminId, review_note: '관리자 보정 추가'
|
||||
}, conn);
|
||||
await vacationBalanceModel.deductDays(user_id, vacation_type_id, year, daysVal, conn);
|
||||
await conn.commit();
|
||||
|
||||
res.status(201).json({ success: true, message: '휴가가 등록되었습니다', data: { request_id: result.insertId } });
|
||||
} catch (error) {
|
||||
await conn.rollback();
|
||||
console.error('관리자 보정 추가 오류:', error);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
},
|
||||
|
||||
async adminDeleteRequest(req, res) {
|
||||
const db = getPool();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const results = await vacationRequestModel.getById(id);
|
||||
if (results.length === 0) {
|
||||
return res.status(404).json({ success: false, error: '해당 휴가 기록을 찾을 수 없습니다' });
|
||||
}
|
||||
const existing = results[0];
|
||||
if (existing.status !== 'approved') {
|
||||
return res.status(400).json({ success: false, error: '승인된 기록만 삭제할 수 있습니다' });
|
||||
}
|
||||
|
||||
const adminId = req.user.user_id || req.user.id;
|
||||
const year = new Date(existing.start_date).getFullYear();
|
||||
|
||||
await conn.beginTransaction();
|
||||
await vacationBalanceModel.restoreDays(existing.user_id, existing.vacation_type_id, year, parseFloat(existing.days_used), conn);
|
||||
await vacationRequestModel.updateStatus(id, {
|
||||
status: 'cancelled', reviewed_by: adminId, review_note: '관리자 보정 삭제'
|
||||
}, conn);
|
||||
await conn.commit();
|
||||
|
||||
res.json({ success: true, message: '휴가가 삭제되었습니다' });
|
||||
} catch (error) {
|
||||
await conn.rollback();
|
||||
console.error('관리자 보정 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ const vacationBalanceModel = {
|
||||
return result;
|
||||
},
|
||||
|
||||
async deductDays(userId, vacationTypeId, year, daysToDeduct) {
|
||||
const db = getPool();
|
||||
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()
|
||||
@@ -71,8 +71,8 @@ const vacationBalanceModel = {
|
||||
return result;
|
||||
},
|
||||
|
||||
async restoreDays(userId, vacationTypeId, year, daysToRestore) {
|
||||
const db = getPool();
|
||||
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()
|
||||
|
||||
@@ -90,7 +90,7 @@ const vacationDashboardModel = {
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
su.user_id, su.name, su.username,
|
||||
vr.start_date, vr.end_date, vr.days_used,
|
||||
vr.request_id, vr.start_date, vr.end_date, vr.days_used,
|
||||
vt.type_code, vt.type_name
|
||||
FROM sso_users su
|
||||
LEFT JOIN sp_vacation_requests vr
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const { getPool } = require('../middleware/auth');
|
||||
|
||||
const vacationRequestModel = {
|
||||
async create(data) {
|
||||
const db = getPool();
|
||||
async create(data, conn) {
|
||||
const db = conn || getPool();
|
||||
const [result] = await db.query('INSERT INTO sp_vacation_requests SET ?', data);
|
||||
return result;
|
||||
},
|
||||
@@ -85,8 +85,8 @@ const vacationRequestModel = {
|
||||
return result;
|
||||
},
|
||||
|
||||
async updateStatus(requestId, statusData) {
|
||||
const db = getPool();
|
||||
async updateStatus(requestId, statusData, conn) {
|
||||
const db = conn || getPool();
|
||||
const [result] = await db.query(`
|
||||
UPDATE sp_vacation_requests
|
||||
SET status = ?, reviewed_by = ?, reviewed_at = NOW(), review_note = ?
|
||||
|
||||
@@ -29,6 +29,10 @@ router.get('/balance/all', requireAdmin, ctrl.getAllBalances);
|
||||
router.get('/balance/:userId', requireAdmin, ctrl.getUserBalance);
|
||||
router.post('/balance/allocate', requireAdmin, ctrl.allocateBalance);
|
||||
|
||||
// 관리자 보정
|
||||
router.post('/admin/correct', requireAdmin, ctrl.adminCreateRequest);
|
||||
router.delete('/admin/requests/:id', requireAdmin, ctrl.adminDeleteRequest);
|
||||
|
||||
// 사용자 목록 (관리자)
|
||||
router.get('/users', requireAdmin, ctrl.getUsers);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user