/** * 대시보드 개인 요약 모델 * Sprint 003 — 연차/연장근로/접근 페이지 통합 조회 */ const { getDb } = require('../config/database'); const OVERTIME_THRESHOLD = 8; // 연장근로 기준 시간 const DashboardModel = { /** * 사용자 정보 조회 (쿼리 1 — 먼저 실행) */ getUserInfo: async (userId) => { const db = await getDb(); const [rows] = await db.execute(` SELECT u.user_id, u.name, u.role, w.worker_id, w.worker_name, w.job_type, COALESCE(w.department_id, u.department_id) AS department_id, COALESCE(d.department_name, d2.department_name, '미배정') AS department_name FROM sso_users u LEFT JOIN workers w ON u.user_id = w.user_id LEFT JOIN departments d ON w.department_id = d.department_id LEFT JOIN departments d2 ON u.department_id = d2.department_id WHERE u.user_id = ? `, [userId]); return rows[0] || null; }, /** * 연차 현황 조회 (쿼리 2) */ getVacationBalance: async (userId, year) => { if (!userId) return []; const db = await getDb(); const [rows] = await db.execute(` SELECT svb.vacation_type_id, svb.total_days, svb.used_days, (svb.total_days - svb.used_days) AS remaining_days, svb.balance_type, svb.expires_at, vt.type_name, vt.type_code FROM sp_vacation_balances svb JOIN vacation_types vt ON svb.vacation_type_id = vt.id WHERE svb.user_id = ? AND svb.year = ? ORDER BY vt.priority `, [userId, year]); return rows; }, /** * 월간 연장근로 조회 (쿼리 3) */ getMonthlyOvertime: async (userId, year, month) => { const db = await getDb(); const [rows] = await db.execute(` SELECT COUNT(CASE WHEN dar.total_work_hours > ${OVERTIME_THRESHOLD} THEN 1 END) AS overtime_days, COALESCE(SUM(CASE WHEN dar.total_work_hours > ${OVERTIME_THRESHOLD} THEN dar.total_work_hours - ${OVERTIME_THRESHOLD} ELSE 0 END), 0) AS total_overtime_hours, COUNT(*) AS total_work_days, COALESCE(SUM(dar.total_work_hours), 0) AS total_work_hours, COALESCE(AVG(dar.total_work_hours), 0) AS avg_daily_hours FROM daily_attendance_records dar WHERE dar.user_id = ? AND YEAR(dar.record_date) = ? AND MONTH(dar.record_date) = ? AND dar.total_work_hours > 0 `, [userId, year, month]); return rows[0] || { overtime_days: 0, total_overtime_hours: 0, total_work_days: 0, total_work_hours: 0, avg_daily_hours: 0 }; }, /** * 접근 가능 페이지 조회 (쿼리 4) */ getQuickAccess: async (userId, departmentId, role) => { const db = await getDb(); const isAdmin = ['admin', 'system'].includes((role || '').toLowerCase()); // 모든 페이지 조회 const [allPages] = await db.execute(` SELECT id, page_key, page_name, page_path, category, is_admin_only FROM pages ORDER BY display_order, page_name `); if (isAdmin) { return { department_pages: allPages.map(formatPage), personal_pages: [], admin_pages: [] }; } // 부서 권한 페이지 // department_page_permissions.page_name은 's1.work.tbm' 형식 (시스템 접두사 포함) // pages.page_key는 'work.tbm' 형식 (접두사 없음) // → 's1.' 접두사를 제거하여 매칭 let deptPageKeys = new Set(); if (departmentId) { const [deptRows] = await db.execute(` SELECT dpp.page_name FROM department_page_permissions dpp WHERE dpp.department_id = ? AND dpp.can_access = 1 `, [departmentId]); deptRows.forEach(r => { const key = r.page_name.startsWith('s1.') ? r.page_name.slice(3) : r.page_name; deptPageKeys.add(key); }); } // 개인 권한 페이지 (user_page_permissions.page_name 기반) const [personalRows] = await db.execute(` SELECT upp.page_name FROM user_page_permissions upp WHERE upp.user_id = ? AND upp.can_access = 1 `, [userId]); const personalPageKeys = new Set(); personalRows.forEach(r => { const key = r.page_name.startsWith('s1.') ? r.page_name.slice(3) : r.page_name; personalPageKeys.add(key); }); // 분류 (부서 우선, 중복 없음 — 권한 있는 페이지만) const departmentPages = []; const personalPages = []; for (const page of allPages) { if (deptPageKeys.has(page.page_key)) { departmentPages.push(formatPage(page)); } else if (personalPageKeys.has(page.page_key)) { personalPages.push(formatPage(page)); } } return { department_pages: departmentPages, personal_pages: personalPages, admin_pages: [] }; } }; function formatPage(page) { return { page_key: page.page_key, page_name: page.page_name, page_path: page.page_path, icon: '', category: page.category || '' }; } module.exports = DashboardModel;