diff --git a/tksupport/api/controllers/companyHolidayController.js b/tksupport/api/controllers/companyHolidayController.js
new file mode 100644
index 0000000..1415805
--- /dev/null
+++ b/tksupport/api/controllers/companyHolidayController.js
@@ -0,0 +1,76 @@
+const companyHolidayModel = require('../models/companyHolidayModel');
+
+const companyHolidayController = {
+ async getHolidays(req, res) {
+ try {
+ const year = parseInt(req.query.year) || new Date().getFullYear();
+ const holidays = await companyHolidayModel.getByYear(year);
+ res.json({ success: true, data: holidays });
+ } catch (error) {
+ console.error('전사 휴가 조회 오류:', error);
+ res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
+ }
+ },
+
+ async createHoliday(req, res) {
+ try {
+ const { holiday_date, holiday_name, holiday_type, description } = req.body;
+ if (!holiday_date || !holiday_name || !holiday_type) {
+ return res.status(400).json({ success: false, error: '필수 필드가 누락되었습니다' });
+ }
+ if (!['PAID', 'ANNUAL_DEDUCT'].includes(holiday_type)) {
+ return res.status(400).json({ success: false, error: '유효하지 않은 휴가 유형입니다' });
+ }
+
+ const created_by = req.user.user_id || req.user.id;
+ const result = await companyHolidayModel.create({ holiday_date, holiday_name, holiday_type, description, created_by });
+ res.status(201).json({ success: true, message: '전사 휴가가 등록되었습니다', data: { id: result.insertId } });
+ } catch (error) {
+ if (error.code === 'ER_DUP_ENTRY') {
+ return res.status(400).json({ success: false, error: '해당 날짜에 이미 전사 휴가가 등록되어 있습니다' });
+ }
+ console.error('전사 휴가 등록 오류:', error);
+ res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
+ }
+ },
+
+ async deleteHoliday(req, res) {
+ try {
+ const { id } = req.params;
+ const holiday = await companyHolidayModel.getById(id);
+ if (!holiday) {
+ return res.status(404).json({ success: false, error: '해당 전사 휴가를 찾을 수 없습니다' });
+ }
+ if (holiday.deduction_applied_at) {
+ return res.status(400).json({ success: false, error: '차감이 실행된 휴가는 삭제할 수 없습니다' });
+ }
+
+ await companyHolidayModel.delete(id);
+ res.json({ success: true, message: '전사 휴가가 삭제되었습니다' });
+ } catch (error) {
+ console.error('전사 휴가 삭제 오류:', error);
+ res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
+ }
+ },
+
+ async applyDeduction(req, res) {
+ try {
+ const { id } = req.params;
+ const result = await companyHolidayModel.applyAnnualDeduction(id);
+ const response = {
+ success: true,
+ message: `연차 차감이 완료되었습니다 (${result.affected_count}명 적용)`,
+ data: result
+ };
+ if (result.missing_balance_count > 0) {
+ response.warning = `${result.missing_balance_count}명의 사원은 연차 잔여일 데이터가 없어 차감되지 않았습니다`;
+ }
+ res.json(response);
+ } catch (error) {
+ console.error('연차 차감 오류:', error);
+ res.status(400).json({ success: false, error: error.message });
+ }
+ }
+};
+
+module.exports = companyHolidayController;
diff --git a/tksupport/api/controllers/vacationController.js b/tksupport/api/controllers/vacationController.js
index e4386a8..23bf2c8 100644
--- a/tksupport/api/controllers/vacationController.js
+++ b/tksupport/api/controllers/vacationController.js
@@ -1,5 +1,6 @@
const vacationRequestModel = require('../models/vacationRequestModel');
const vacationBalanceModel = require('../models/vacationBalanceModel');
+const companyHolidayModel = require('../models/companyHolidayModel');
const { getPool } = require('../middleware/auth');
const vacationController = {
@@ -228,6 +229,29 @@ const vacationController = {
}
},
+ // ─── 내 휴가 현황 ───
+
+ async getMyStatus(req, res) {
+ try {
+ const userId = req.user.user_id || req.user.id;
+ const year = parseInt(req.query.year) || new Date().getFullYear();
+ const startOfYear = `${year}-01-01`;
+ const endOfYear = `${year}-12-31`;
+ const [balances, requests, holidays] = await Promise.all([
+ vacationBalanceModel.getByUserAndYear(userId, year),
+ vacationRequestModel.getAll({ user_id: userId, start_date: startOfYear, end_date: endOfYear }),
+ companyHolidayModel.getByYear(year)
+ ]);
+ res.json({
+ success: true,
+ data: { balances, requests, company_holidays: holidays, overtime_hours: null }
+ });
+ } catch (error) {
+ console.error('내 휴가 현황 조회 오류:', error);
+ res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
+ }
+ },
+
// ─── 잔여일 ───
async getMyBalance(req, res) {
diff --git a/tksupport/api/controllers/vacationDashboardController.js b/tksupport/api/controllers/vacationDashboardController.js
new file mode 100644
index 0000000..b1e262c
--- /dev/null
+++ b/tksupport/api/controllers/vacationDashboardController.js
@@ -0,0 +1,52 @@
+const vacationDashboardModel = require('../models/vacationDashboardModel');
+
+const vacationDashboardController = {
+ async getDashboard(req, res) {
+ try {
+ const year = parseInt(req.query.year) || new Date().getFullYear();
+ const filters = {
+ department_id: req.query.department_id || null,
+ search_name: req.query.search_name || null
+ };
+
+ const [summary, rows] = await Promise.all([
+ vacationDashboardModel.getSummary(year),
+ vacationDashboardModel.getEmployeeList(year, filters)
+ ]);
+
+ // user_id별 그룹핑
+ const employeeMap = {};
+ rows.forEach(row => {
+ if (!employeeMap[row.user_id]) {
+ employeeMap[row.user_id] = {
+ user_id: row.user_id,
+ name: row.name,
+ username: row.username,
+ department_name: row.department_name,
+ hire_date: row.hire_date,
+ balances: []
+ };
+ }
+ if (row.id) {
+ employeeMap[row.user_id].balances.push({
+ balance_type: row.balance_type,
+ type_name: row.type_name,
+ type_code: row.type_code,
+ total_days: row.total_days,
+ used_days: row.used_days,
+ remaining_days: row.remaining_days,
+ expires_at: row.expires_at
+ });
+ }
+ });
+ const employees = Object.values(employeeMap);
+
+ res.json({ success: true, data: { summary, employees } });
+ } catch (error) {
+ console.error('휴가 대시보드 조회 오류:', error);
+ res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' });
+ }
+ }
+};
+
+module.exports = vacationDashboardController;
diff --git a/tksupport/api/db/migrations/002_section_c_additions.sql b/tksupport/api/db/migrations/002_section_c_additions.sql
new file mode 100644
index 0000000..410851a
--- /dev/null
+++ b/tksupport/api/db/migrations/002_section_c_additions.sql
@@ -0,0 +1,25 @@
+-- Section A 동시 개발 대비: 필요 테이블/컬럼 IF NOT EXISTS로 안전 생성
+
+CREATE TABLE IF NOT EXISTS company_holidays (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ holiday_date DATE NOT NULL,
+ holiday_name VARCHAR(100) NOT NULL,
+ holiday_type ENUM('PAID', 'ANNUAL_DEDUCT') NOT NULL,
+ description TEXT,
+ created_by INT NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (created_by) REFERENCES sso_users(user_id),
+ UNIQUE KEY uq_holiday_date (holiday_date)
+);
+
+-- Section C 전용: 중복 차감 방지
+ALTER TABLE company_holidays
+ ADD COLUMN IF NOT EXISTS deduction_applied_at TIMESTAMP NULL
+ COMMENT '연차차감 실행 시각 (NULL=미실행)';
+
+-- sp_vacation_balances 컬럼 추가 (Section A 대비)
+ALTER TABLE sp_vacation_balances
+ ADD COLUMN IF NOT EXISTS balance_type ENUM('AUTO','MANUAL','CARRY_OVER','LONG_SERVICE','COMPANY_GRANT')
+ DEFAULT 'AUTO';
+ALTER TABLE sp_vacation_balances
+ ADD COLUMN IF NOT EXISTS expires_at DATE NULL;
diff --git a/tksupport/api/index.js b/tksupport/api/index.js
index 511fe58..44c20fb 100644
--- a/tksupport/api/index.js
+++ b/tksupport/api/index.js
@@ -1,6 +1,8 @@
const express = require('express');
const cors = require('cors');
const vacationRoutes = require('./routes/vacationRoutes');
+const companyHolidayRoutes = require('./routes/companyHolidayRoutes');
+const vacationDashboardRoutes = require('./routes/vacationDashboardRoutes');
const vacationRequestModel = require('./models/vacationRequestModel');
const { requireAuth } = require('./middleware/auth');
@@ -34,6 +36,8 @@ app.get('/health', (req, res) => {
});
// Routes
+app.use('/api/vacation/company', companyHolidayRoutes);
+app.use('/api/vacation/dashboard', vacationDashboardRoutes);
app.use('/api/vacation', vacationRoutes);
// 404
diff --git a/tksupport/api/middleware/auth.js b/tksupport/api/middleware/auth.js
index 5869260..0cf54fc 100644
--- a/tksupport/api/middleware/auth.js
+++ b/tksupport/api/middleware/auth.js
@@ -75,4 +75,21 @@ function requirePage(pageName) {
};
}
-module.exports = { getPool, extractToken, requireAuth, requireAdmin, requirePage };
+function requireSupportTeam(req, res, next) {
+ const token = extractToken(req);
+ if (!token) {
+ return res.status(401).json({ success: false, error: '인증이 필요합니다' });
+ }
+ try {
+ const decoded = jwt.verify(token, JWT_SECRET);
+ if (!['support_team', 'admin', 'system'].includes((decoded.role || '').toLowerCase())) {
+ return res.status(403).json({ success: false, error: '지원팀 이상 권한이 필요합니다' });
+ }
+ req.user = decoded;
+ next();
+ } catch {
+ return res.status(401).json({ success: false, error: '유효하지 않은 토큰입니다' });
+ }
+}
+
+module.exports = { getPool, extractToken, requireAuth, requireAdmin, requireSupportTeam, requirePage };
diff --git a/tksupport/api/models/companyHolidayModel.js b/tksupport/api/models/companyHolidayModel.js
new file mode 100644
index 0000000..1d93cf9
--- /dev/null
+++ b/tksupport/api/models/companyHolidayModel.js
@@ -0,0 +1,93 @@
+const { getPool } = require('../middleware/auth');
+
+const companyHolidayModel = {
+ async getByYear(year) {
+ const db = getPool();
+ const [rows] = await db.query(
+ 'SELECT * FROM company_holidays WHERE YEAR(holiday_date) = ? ORDER BY holiday_date',
+ [year]
+ );
+ return rows;
+ },
+
+ async getById(id) {
+ const db = getPool();
+ const [rows] = await db.query('SELECT * FROM company_holidays WHERE id = ?', [id]);
+ return rows.length > 0 ? rows[0] : null;
+ },
+
+ async create(data) {
+ const db = getPool();
+ const [result] = await db.query(
+ 'INSERT INTO company_holidays (holiday_date, holiday_name, holiday_type, description, created_by) VALUES (?, ?, ?, ?, ?)',
+ [data.holiday_date, data.holiday_name, data.holiday_type, data.description || null, data.created_by]
+ );
+ return result;
+ },
+
+ async delete(id) {
+ const db = getPool();
+ const [result] = await db.query('DELETE FROM company_holidays WHERE id = ?', [id]);
+ return result;
+ },
+
+ async applyAnnualDeduction(holidayId) {
+ const db = getPool();
+ const conn = await db.getConnection();
+ try {
+ await conn.beginTransaction();
+
+ // 1. SELECT FOR UPDATE — 동시 실행 방지
+ const [holidays] = await conn.query(
+ 'SELECT * FROM company_holidays WHERE id = ? FOR UPDATE', [holidayId]
+ );
+ if (holidays.length === 0) {
+ throw new Error('해당 전사 휴가를 찾을 수 없습니다');
+ }
+ const holiday = holidays[0];
+ if (holiday.holiday_type !== 'ANNUAL_DEDUCT') {
+ throw new Error('연차차감 유형의 휴가만 차감할 수 있습니다');
+ }
+ if (holiday.deduction_applied_at) {
+ throw new Error('이미 차감이 실행된 휴가입니다');
+ }
+
+ // 2. vacation_types에서 ANNUAL_FULL id 조회
+ const [types] = await conn.query(
+ "SELECT id FROM vacation_types WHERE type_code = 'ANNUAL_FULL'"
+ );
+ if (types.length === 0) {
+ throw new Error('연차(ANNUAL_FULL) 유형이 존재하지 않습니다');
+ }
+ const typeId = types[0].id;
+
+ // 3. 전 활성 사원 연차 차감 (잔여일 무관 일괄 적용)
+ const [result] = await conn.query(`
+ UPDATE sp_vacation_balances SET used_days = used_days + 1, updated_at = NOW()
+ WHERE vacation_type_id = ? AND year = YEAR(?) AND balance_type = 'AUTO'
+ AND user_id IN (SELECT user_id FROM sso_users WHERE is_active = 1)
+ `, [typeId, holiday.holiday_date]);
+
+ // 4. 차감 완료 표시
+ await conn.query(
+ 'UPDATE company_holidays SET deduction_applied_at = NOW() WHERE id = ?', [holidayId]
+ );
+
+ // 5. balance 없는 사원 수 체크 (경고용)
+ const [activeUsers] = await conn.query(
+ 'SELECT COUNT(*) as cnt FROM sso_users WHERE is_active = 1'
+ );
+ const missing = activeUsers[0].cnt - result.affectedRows;
+
+ await conn.commit();
+ return { affected_count: result.affectedRows, missing_balance_count: missing };
+ } catch (err) {
+ await conn.rollback();
+ throw err;
+ } finally {
+ conn.release();
+ }
+ }
+};
+
+module.exports = companyHolidayModel;
diff --git a/tksupport/api/models/vacationBalanceModel.js b/tksupport/api/models/vacationBalanceModel.js
index 9236a23..d7a0a66 100644
--- a/tksupport/api/models/vacationBalanceModel.js
+++ b/tksupport/api/models/vacationBalanceModel.js
@@ -6,6 +6,8 @@ const vacationBalanceModel = {
const [rows] = await db.query(`
SELECT
vb.*,
+ vb.balance_type,
+ vb.expires_at,
vt.type_name,
vt.type_code,
vt.priority,
@@ -13,7 +15,7 @@ const vacationBalanceModel = {
(vb.total_days - vb.used_days) as remaining_days
FROM sp_vacation_balances vb
INNER JOIN vacation_types vt ON vb.vacation_type_id = vt.id
- WHERE vb.user_id = ? AND vb.year = ?
+ WHERE vb.user_id = ? AND (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL))
ORDER BY vt.priority ASC, vt.type_name ASC
`, [userId, year]);
return rows;
@@ -24,9 +26,12 @@ const vacationBalanceModel = {
const [rows] = await db.query(`
SELECT
vb.*,
+ vb.balance_type,
su.name as user_name,
su.username,
su.hire_date,
+ su.department_id,
+ COALESCE(d.department_name, '미배정') as department_name,
vt.type_name,
vt.type_code,
vt.priority,
@@ -34,7 +39,8 @@ const vacationBalanceModel = {
FROM sp_vacation_balances vb
INNER JOIN sso_users su ON vb.user_id = su.user_id
INNER JOIN vacation_types vt ON vb.vacation_type_id = vt.id
- WHERE vb.year = ? AND su.is_active = 1
+ LEFT JOIN departments d ON su.department_id = d.department_id
+ WHERE (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL)) AND su.is_active = 1
ORDER BY su.name ASC, vt.priority ASC
`, [year]);
return rows;
@@ -42,14 +48,16 @@ const vacationBalanceModel = {
async allocate(data) {
const db = getPool();
+ const balanceType = data.balance_type || 'AUTO';
+ const expiresAt = data.expires_at || null;
const [result] = await db.query(`
- INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by)
- VALUES (?, ?, ?, ?, 0, ?, ?)
+ INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at)
+ VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
total_days = VALUES(total_days),
notes = VALUES(notes),
updated_at = NOW()
- `, [data.user_id, data.vacation_type_id, data.year, data.total_days, data.notes || null, data.created_by]);
+ `, [data.user_id, data.vacation_type_id, data.year, data.total_days, data.notes || null, data.created_by, balanceType, expiresAt]);
return result;
},
diff --git a/tksupport/api/models/vacationDashboardModel.js b/tksupport/api/models/vacationDashboardModel.js
new file mode 100644
index 0000000..f42c98f
--- /dev/null
+++ b/tksupport/api/models/vacationDashboardModel.js
@@ -0,0 +1,55 @@
+const { getPool } = require('../middleware/auth');
+
+const vacationDashboardModel = {
+ async getSummary(year) {
+ const db = getPool();
+ const [rows] = await db.query(`
+ SELECT
+ d.department_id, COALESCE(d.department_name, '미배정') as department_name,
+ COUNT(DISTINCT su.user_id) as employee_count,
+ AVG(vb.total_days - vb.used_days) as avg_remaining,
+ SUM(CASE WHEN (vb.total_days - vb.used_days) <= 2 THEN 1 ELSE 0 END) as low_balance_count
+ FROM sso_users su
+ LEFT JOIN departments d ON su.department_id = d.department_id
+ LEFT JOIN sp_vacation_balances vb ON su.user_id = vb.user_id
+ AND vb.year = ? AND vb.balance_type = 'AUTO'
+ WHERE su.is_active = 1
+ GROUP BY d.department_id
+ `, [year]);
+ return rows;
+ },
+
+ async getEmployeeList(year, filters = {}) {
+ const db = getPool();
+ let query = `
+ SELECT su.user_id, su.name, su.username, su.hire_date, su.department_id,
+ COALESCE(d.department_name, '미배정') as department_name,
+ vb.id, vb.balance_type, vb.total_days, vb.used_days,
+ (vb.total_days - vb.used_days) as remaining_days,
+ vb.expires_at, vt.type_name, vt.type_code
+ FROM sso_users su
+ LEFT JOIN departments d ON su.department_id = d.department_id
+ LEFT JOIN sp_vacation_balances vb ON su.user_id = vb.user_id
+ AND (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL))
+ LEFT JOIN vacation_types vt ON vb.vacation_type_id = vt.id
+ WHERE su.is_active = 1
+ `;
+ const params = [year];
+
+ if (filters.department_id) {
+ query += ' AND su.department_id = ?';
+ params.push(filters.department_id);
+ }
+ if (filters.search_name) {
+ query += ' AND su.name LIKE ?';
+ params.push(`%${filters.search_name}%`);
+ }
+
+ query += ' ORDER BY su.name ASC';
+
+ const [rows] = await db.query(query, params);
+ return rows;
+ }
+};
+
+module.exports = vacationDashboardModel;
diff --git a/tksupport/api/models/vacationRequestModel.js b/tksupport/api/models/vacationRequestModel.js
index a33f612..71c388e 100644
--- a/tksupport/api/models/vacationRequestModel.js
+++ b/tksupport/api/models/vacationRequestModel.js
@@ -14,11 +14,13 @@ const vacationRequestModel = {
vr.*,
su.name as user_name,
su.username,
+ COALESCE(d.department_name, '미배정') as department_name,
vt.type_name as vacation_type_name,
vt.type_code,
reviewer.name as reviewer_name
FROM sp_vacation_requests vr
INNER JOIN sso_users su ON vr.user_id = su.user_id
+ LEFT JOIN departments d ON su.department_id = d.department_id
INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id
LEFT JOIN sso_users reviewer ON vr.reviewed_by = reviewer.user_id
WHERE 1=1
@@ -45,6 +47,10 @@ const vacationRequestModel = {
query += ' AND vr.vacation_type_id = ?';
params.push(filters.vacation_type_id);
}
+ if (filters.department_id) {
+ query += ' AND su.department_id = ?';
+ params.push(filters.department_id);
+ }
query += ' ORDER BY vr.created_at DESC';
@@ -59,11 +65,13 @@ const vacationRequestModel = {
vr.*,
su.name as user_name,
su.username,
+ COALESCE(d.department_name, '미배정') as department_name,
vt.type_name as vacation_type_name,
vt.type_code,
reviewer.name as reviewer_name
FROM sp_vacation_requests vr
INNER JOIN sso_users su ON vr.user_id = su.user_id
+ LEFT JOIN departments d ON su.department_id = d.department_id
INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id
LEFT JOIN sso_users reviewer ON vr.reviewed_by = reviewer.user_id
WHERE vr.request_id = ?
@@ -113,10 +121,12 @@ const vacationRequestModel = {
vr.*,
su.name as user_name,
su.username,
+ COALESCE(d.department_name, '미배정') as department_name,
vt.type_name as vacation_type_name,
vt.type_code
FROM sp_vacation_requests vr
INNER JOIN sso_users su ON vr.user_id = su.user_id
+ LEFT JOIN departments d ON su.department_id = d.department_id
INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id
WHERE vr.status = 'pending'
ORDER BY vr.created_at ASC
@@ -128,17 +138,21 @@ const vacationRequestModel = {
const db = getPool();
const fs = require('fs');
const path = require('path');
- const sqlFile = path.join(__dirname, '..', 'db', 'migrations', '001_create_sp_tables.sql');
- const sql = fs.readFileSync(sqlFile, 'utf8');
- const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
- for (const stmt of statements) {
- try {
- await db.query(stmt);
- } catch (err) {
- if (err.code === 'ER_DUP_FIELDNAME' || err.code === 'ER_TABLE_EXISTS_ERROR') {
- // Already migrated
- } else {
- throw err;
+ const migrationFiles = ['001_create_sp_tables.sql', '002_section_c_additions.sql'];
+ for (const file of migrationFiles) {
+ const sqlFile = path.join(__dirname, '..', 'db', 'migrations', file);
+ if (!fs.existsSync(sqlFile)) continue;
+ const sql = fs.readFileSync(sqlFile, 'utf8');
+ const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
+ for (const stmt of statements) {
+ try {
+ await db.query(stmt);
+ } catch (err) {
+ if (err.code === 'ER_DUP_FIELDNAME' || err.code === 'ER_TABLE_EXISTS_ERROR') {
+ // Already migrated
+ } else {
+ throw err;
+ }
}
}
}
diff --git a/tksupport/api/routes/companyHolidayRoutes.js b/tksupport/api/routes/companyHolidayRoutes.js
new file mode 100644
index 0000000..6e5bb8a
--- /dev/null
+++ b/tksupport/api/routes/companyHolidayRoutes.js
@@ -0,0 +1,13 @@
+const express = require('express');
+const router = express.Router();
+const { requireAuth, requireSupportTeam } = require('../middleware/auth');
+const ctrl = require('../controllers/companyHolidayController');
+
+router.use(requireAuth);
+
+router.get('/holidays', ctrl.getHolidays);
+router.post('/holidays', requireSupportTeam, ctrl.createHoliday);
+router.delete('/holidays/:id', requireSupportTeam, ctrl.deleteHoliday);
+router.post('/holidays/:id/apply-deduction', requireSupportTeam, ctrl.applyDeduction);
+
+module.exports = router;
diff --git a/tksupport/api/routes/vacationDashboardRoutes.js b/tksupport/api/routes/vacationDashboardRoutes.js
new file mode 100644
index 0000000..6dfc382
--- /dev/null
+++ b/tksupport/api/routes/vacationDashboardRoutes.js
@@ -0,0 +1,10 @@
+const express = require('express');
+const router = express.Router();
+const { requireAuth, requireSupportTeam } = require('../middleware/auth');
+const ctrl = require('../controllers/vacationDashboardController');
+
+router.use(requireAuth);
+
+router.get('/', requireSupportTeam, ctrl.getDashboard);
+
+module.exports = router;
diff --git a/tksupport/api/routes/vacationRoutes.js b/tksupport/api/routes/vacationRoutes.js
index 46488ff..194136c 100644
--- a/tksupport/api/routes/vacationRoutes.js
+++ b/tksupport/api/routes/vacationRoutes.js
@@ -20,6 +20,9 @@ router.get('/pending', requireAdmin, ctrl.getPending);
router.patch('/requests/:id/approve', requireAdmin, ctrl.approveRequest);
router.patch('/requests/:id/reject', requireAdmin, ctrl.rejectRequest);
+// 내 휴가 현황
+router.get('/my-status', ctrl.getMyStatus);
+
// 잔여일
router.get('/balance', ctrl.getMyBalance);
router.get('/balance/all', requireAdmin, ctrl.getAllBalances);
diff --git a/tksupport/web/company-holidays.html b/tksupport/web/company-holidays.html
new file mode 100644
index 0000000..6d704b9
--- /dev/null
+++ b/tksupport/web/company-holidays.html
@@ -0,0 +1,229 @@
+
+
+
+
+
+ 전사 휴가 관리 - TK 행정지원
+
+
+
+
+
+
+
+
+
+
+
+
TK 행정지원
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
전사 휴가 관리
+
+
+
+
+
+
+
+
+
+
+ | 날짜 |
+ 휴가명 |
+ 유형 |
+ 설명 |
+ 상태 |
+ 관리 |
+
+
+
+ | 로딩 중... |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tksupport/web/index.html b/tksupport/web/index.html
index 3cef246..b7130c8 100644
--- a/tksupport/web/index.html
+++ b/tksupport/web/index.html
@@ -6,7 +6,7 @@
대시보드 - TK 행정지원
-
+
@@ -123,7 +123,7 @@
-
+
-
+
@@ -54,6 +54,7 @@
| 신청자 |
+ 부서 |
유형 |
기간 |
일수 |
@@ -63,7 +64,7 @@
- | 로딩 중... |
+ | 로딩 중... |
@@ -83,6 +84,12 @@
+
+
+
+
@@ -92,6 +99,7 @@
| 신청자 |
+ 부서 |
유형 |
기간 |
일수 |
@@ -101,7 +109,7 @@
- | 조회 버튼을 클릭하세요 |
+ | 조회 버튼을 클릭하세요 |
@@ -195,7 +203,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
TK 행정지원
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
직원별 상세
+
+
+
+
+ | 이름 |
+ 부서 |
+ 기본연차 |
+ 이월 |
+ 장기근속 |
+ 총 잔여 |
+
+
+
+ | 로딩 중... |
+
+
+
+
+
+
+
+
+
+
+
+