From 71289be3750b486227c6172d3df126acc82b167f Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 25 Mar 2026 12:34:56 +0900 Subject: [PATCH] =?UTF-8?q?feat(tksupport):=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=9C=B4=EA=B0=80=EA=B4=80=EB=A6=AC=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EA=B0=9C=ED=8E=B8=20=E2=80=94=20=EC=97=B0?= =?UTF-8?q?=EA=B0=84=20=EC=B4=9D=EA=B4=84=20+=20=EC=9B=94=EA=B0=84=20?= =?UTF-8?q?=EC=BA=98=EB=A6=B0=EB=8D=94=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vacationDashboardController.js | 33 ++ .../api/models/vacationDashboardModel.js | 64 +++ .../api/routes/vacationDashboardRoutes.js | 2 + tksupport/web/vacation-dashboard.html | 429 ++++++++++++------ 4 files changed, 389 insertions(+), 139 deletions(-) diff --git a/tksupport/api/controllers/vacationDashboardController.js b/tksupport/api/controllers/vacationDashboardController.js index b1e262c..de9eb22 100644 --- a/tksupport/api/controllers/vacationDashboardController.js +++ b/tksupport/api/controllers/vacationDashboardController.js @@ -46,6 +46,39 @@ const vacationDashboardController = { console.error('휴가 대시보드 조회 오류:', error); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); } + }, + + async getYearlyOverview(req, res) { + try { + const year = parseInt(req.query.year) || new Date().getFullYear(); + const [users, balances] = await Promise.all([ + vacationDashboardModel.getYearlyOverview(year), + vacationDashboardModel.getBalances(year) + ]); + res.json({ success: true, data: { users, balances } }); + } catch (error) { + console.error('연간 총괄 조회 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + }, + + async getMonthlyDetail(req, res) { + try { + const year = parseInt(req.query.year) || new Date().getFullYear(); + const month = parseInt(req.query.month); + const departmentId = parseInt(req.query.department_id) || 0; + if (!month || month < 1 || month > 12) { + return res.status(400).json({ success: false, error: '유효하지 않은 월입니다' }); + } + const [records, holidays] = await Promise.all([ + vacationDashboardModel.getMonthlyDetail(year, month, departmentId), + vacationDashboardModel.getHolidays(year, month) + ]); + res.json({ success: true, data: { records, holidays } }); + } catch (error) { + console.error('월간 상세 조회 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } } }; diff --git a/tksupport/api/models/vacationDashboardModel.js b/tksupport/api/models/vacationDashboardModel.js index f42c98f..41559a9 100644 --- a/tksupport/api/models/vacationDashboardModel.js +++ b/tksupport/api/models/vacationDashboardModel.js @@ -49,6 +49,70 @@ const vacationDashboardModel = { const [rows] = await db.query(query, params); return rows; + }, + + // View 1: 연간 총괄 — 전직원 월별 사용 합계 + async getYearlyOverview(year) { + const db = getPool(); + const [rows] = await db.query(` + SELECT + su.user_id, su.name, su.username, + COALESCE(d.department_id, 0) as department_id, + COALESCE(d.department_name, '미배정') as department_name, + MONTH(vr.start_date) as month, + SUM(vr.days_used) as total_days + FROM sso_users su + LEFT JOIN departments d ON su.department_id = d.department_id + LEFT JOIN sp_vacation_requests vr + ON su.user_id = vr.user_id AND vr.status = 'approved' AND YEAR(vr.start_date) = ? + WHERE su.is_active = 1 AND su.hire_date IS NOT NULL + GROUP BY su.user_id, MONTH(vr.start_date) + ORDER BY d.department_name, su.name + `, [year]); + return rows; + }, + + // View 1: 연간 부여/사용 잔액 + async getBalances(year) { + const db = getPool(); + const [rows] = await db.query(` + SELECT user_id, SUM(total_days) as granted, SUM(used_days) as used + FROM sp_vacation_balances + WHERE year = ? AND balance_type = 'AUTO' + GROUP BY user_id + `, [year]); + return rows; + }, + + // View 2: 월간 상세 — 부서 전직원 일별 휴가 ($1=year ON, $2=month ON, $3=deptId WHERE, $4=deptId WHERE) + async getMonthlyDetail(year, month, departmentId) { + const db = getPool(); + const [rows] = await db.query(` + SELECT + su.user_id, su.name, su.username, + 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 + ON su.user_id = vr.user_id AND vr.status = 'approved' + AND YEAR(vr.start_date) = ? AND MONTH(vr.start_date) = ? + LEFT JOIN vacation_types vt ON vr.vacation_type_id = vt.id + WHERE su.is_active = 1 AND su.hire_date IS NOT NULL + AND (su.department_id = ? OR (? = 0 AND su.department_id IS NULL)) + ORDER BY su.name, vr.start_date + `, [year, month, departmentId, departmentId]); + return rows; + }, + + // View 2: 공휴일 표시용 + async getHolidays(year, month) { + const db = getPool(); + const [rows] = await db.query(` + SELECT holiday_date, holiday_name + FROM company_holidays + WHERE YEAR(holiday_date) = ? AND MONTH(holiday_date) = ? + `, [year, month]); + return rows; } }; diff --git a/tksupport/api/routes/vacationDashboardRoutes.js b/tksupport/api/routes/vacationDashboardRoutes.js index 6dfc382..5f9f443 100644 --- a/tksupport/api/routes/vacationDashboardRoutes.js +++ b/tksupport/api/routes/vacationDashboardRoutes.js @@ -6,5 +6,7 @@ const ctrl = require('../controllers/vacationDashboardController'); router.use(requireAuth); router.get('/', requireSupportTeam, ctrl.getDashboard); +router.get('/yearly-overview', requireSupportTeam, ctrl.getYearlyOverview); +router.get('/monthly-detail', requireSupportTeam, ctrl.getMonthlyDetail); module.exports = router; diff --git a/tksupport/web/vacation-dashboard.html b/tksupport/web/vacation-dashboard.html index a57a8c6..050e2ab 100644 --- a/tksupport/web/vacation-dashboard.html +++ b/tksupport/web/vacation-dashboard.html @@ -7,6 +7,14 @@ +
@@ -33,32 +41,6 @@
- -
-
-
- - -
-
- - -
-
- - -
- - -
-
-
@@ -75,33 +57,88 @@
- -
-

부서별 현황

-
-
로딩 중...
+ +
+
+
+
+ + +
+
+ + +
+ +
+

연간 사용현황

+
+ + + + + + + + + + + + + + + + + +
부서이름1월2월3월4월5월6월7월8월9월10월11월12월전체사용잔여
로딩 중...
+
- -
-

직원별 상세

-
- - - - - - - - - - - - - - -
이름부서기본연차이월장기근속총 잔여
로딩 중...
+ +
@@ -110,119 +147,233 @@