Files
tk-factory-services/system1-factory/api/models/dashboardModel.js
Hyungi Ahn 52e6ec16f8 fix(dashboard): workers 없는 사용자 부서 권한 조회 수정
getUserInfo에서 workers.department_id만 사용하여
workers 레코드가 없는 사용자(생산지원팀 등)의 department_id가
NULL이 되어 메뉴가 안 보이던 문제.
COALESCE(w.department_id, u.department_id) fallback 추가.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:01:04 +09:00

149 lines
4.9 KiB
JavaScript

/**
* 대시보드 개인 요약 모델
* 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;