TBM 시스템: - 4단계 워크플로우 (draft→세부편집→완료→작업보고) - 모바일 전용 TBM 페이지 (tbm-mobile.html) + 3단계 생성 위자드 - 작업자 작업 분할 (work_hours + split_seq) - 작업자 이동 보내기/빼오기 (tbm_transfers 테이블) - 생성 시 중복 배정 방지 (당일 배정 현황 조회) - 데스크탑 TBM 페이지 세부편집 기능 추가 작업보고서: - 모바일 전용 작업보고서 페이지 (report-create-mobile.html) - TBM에서 사전 등록된 work_hours 자동 반영 권한 시스템: - tkuser user_page_permissions 테이블과 system1 페이지 접근 연동 - pageAccessRoutes를 userRoutes보다 먼저 등록 (라우트 우선순위 수정) - TKUSER_DEFAULT_ACCESS 폴백 추가 (개인→부서→기본값 3단계) - 권한 캐시키 갱신 (userPageAccess_v2) 기타: - app-init.js 캐시 버스팅 (v=5) - iOS Safari touch-action: manipulation 적용 - KST 타임존 날짜 버그 수정 (toISOString UTC 이슈) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
334 lines
12 KiB
JavaScript
334 lines
12 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { getDb } = require('../dbPool');
|
|
const { requireAuth, requireAdmin } = require('../middlewares/auth');
|
|
|
|
// tkuser page_name → default_access 매핑 (permissionModel.js의 DEFAULT_PAGES와 동기화)
|
|
const TKUSER_DEFAULT_ACCESS = {
|
|
's1.dashboard': true,
|
|
's1.work.tbm': true,
|
|
's1.work.report_create': true,
|
|
's1.work.analysis': false,
|
|
's1.work.nonconformity': true,
|
|
's1.factory.repair_management': false,
|
|
's1.inspection.daily_patrol': false,
|
|
's1.inspection.checkin': true,
|
|
's1.inspection.work_status': false,
|
|
's1.safety.visit_request': true,
|
|
's1.safety.management': false,
|
|
's1.safety.checklist_manage': false,
|
|
's1.attendance.my_vacation_info': true,
|
|
's1.attendance.monthly': true,
|
|
's1.attendance.vacation_request': true,
|
|
's1.attendance.vacation_management': false,
|
|
's1.attendance.vacation_allocation': false,
|
|
's1.attendance.annual_overview': false,
|
|
's1.admin.workers': false,
|
|
's1.admin.projects': false,
|
|
's1.admin.tasks': false,
|
|
's1.admin.workplaces': false,
|
|
's1.admin.equipments': false,
|
|
's1.admin.issue_categories': false,
|
|
's1.admin.attendance_report': false,
|
|
};
|
|
|
|
// system1 page_key → tkuser page_name 매핑
|
|
const PAGEKEY_TO_TKUSER = {
|
|
'dashboard': 's1.dashboard',
|
|
'work.tbm': 's1.work.tbm',
|
|
'work.report-create': 's1.work.report_create',
|
|
'work.report-view': 's1.work.report_create',
|
|
'work.analysis': 's1.work.analysis',
|
|
'work.visit-request': 's1.safety.visit_request',
|
|
'work.issue-report': 's1.work.nonconformity',
|
|
'work.issue-list': 's1.work.nonconformity',
|
|
'work.issue-detail': 's1.work.nonconformity',
|
|
'safety.issue_report': 's1.work.nonconformity',
|
|
'safety.issue_list': 's1.work.nonconformity',
|
|
'safety.issue_detail': 's1.work.nonconformity',
|
|
'safety.checklist_manage': 's1.safety.checklist_manage',
|
|
'admin.workers': 's1.admin.workers',
|
|
'admin.projects': 's1.admin.projects',
|
|
'admin.tasks': 's1.admin.tasks',
|
|
'admin.workplaces': 's1.admin.workplaces',
|
|
'admin.equipments': 's1.admin.equipments',
|
|
'admin.codes': 's1.admin.tasks',
|
|
'admin.safety-management': 's1.safety.management',
|
|
'admin.safety-training-conduct': 's1.safety.management',
|
|
'admin.attendance-report-comparison': 's1.admin.attendance_report',
|
|
'admin.departments': 's1.admin.workers',
|
|
'common.daily-attendance': 's1.inspection.checkin',
|
|
'common.monthly-attendance': 's1.attendance.monthly',
|
|
'common.vacation-request': 's1.attendance.vacation_request',
|
|
'common.vacation-management': 's1.attendance.vacation_management',
|
|
'common.annual-vacation-overview': 's1.attendance.annual_overview',
|
|
'common.vacation-allocation': 's1.attendance.vacation_allocation',
|
|
'inspection.daily_patrol': 's1.inspection.daily_patrol',
|
|
'attendance.vacation_approval': 's1.attendance.vacation_management',
|
|
'attendance.vacation_input': 's1.attendance.vacation_allocation',
|
|
};
|
|
|
|
/**
|
|
* 모든 페이지 목록 조회
|
|
* GET /api/pages
|
|
*/
|
|
router.get('/pages', requireAuth, async (req, res) => {
|
|
try {
|
|
const db = await getDb();
|
|
const [pages] = await db.query(`
|
|
SELECT id, page_key, page_name, page_path, category, description, is_admin_only, display_order
|
|
FROM pages
|
|
ORDER BY display_order, page_name
|
|
`);
|
|
|
|
res.json({ success: true, data: pages });
|
|
} catch (error) {
|
|
console.error('페이지 목록 조회 오류:', error);
|
|
res.status(500).json({ success: false, error: '페이지 목록을 불러오는데 실패했습니다.' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 특정 사용자의 페이지 접근 권한 조회
|
|
* GET /api/users/:userId/page-access
|
|
*/
|
|
router.get('/users/:userId/page-access', requireAuth, async (req, res) => {
|
|
try {
|
|
const ssoUserId = req.params.userId;
|
|
const db = await getDb();
|
|
|
|
// SSO 사용자 조회 (department_id 포함)
|
|
const [ssoRows] = await db.query(
|
|
'SELECT user_id, username, name, role, department_id FROM sso_users WHERE user_id = ?',
|
|
[ssoUserId]
|
|
);
|
|
if (ssoRows.length === 0) {
|
|
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다.' });
|
|
}
|
|
const ssoUser = ssoRows[0];
|
|
|
|
// SSO role로 Admin 체크
|
|
const ssoRole = (ssoUser.role || '').toLowerCase();
|
|
if (ssoRole === 'admin' || ssoRole === 'system') {
|
|
const [allPages] = await db.query(`
|
|
SELECT id, page_key, page_name, page_path, category, is_admin_only
|
|
FROM pages
|
|
ORDER BY display_order, page_name
|
|
`);
|
|
|
|
const pageAccess = allPages.map(page => ({
|
|
page_id: page.id,
|
|
page_key: page.page_key,
|
|
page_name: page.page_name,
|
|
page_path: page.page_path,
|
|
category: page.category,
|
|
is_admin_only: page.is_admin_only,
|
|
can_access: true,
|
|
is_default: true
|
|
}));
|
|
|
|
return res.json({ success: true, data: { user: ssoUser, pageAccess } });
|
|
}
|
|
|
|
// 일반 사용자: tkuser 권한 테이블에서 조회
|
|
// 1) 개인 권한 (user_page_permissions)
|
|
const [userPerms] = await db.query(
|
|
'SELECT page_name, can_access FROM user_page_permissions WHERE user_id = ?',
|
|
[ssoUserId]
|
|
);
|
|
const userPermMap = {};
|
|
userPerms.forEach(p => { userPermMap[p.page_name] = !!p.can_access; });
|
|
|
|
// 2) 부서 권한 (department_page_permissions)
|
|
const deptPermMap = {};
|
|
if (ssoUser.department_id) {
|
|
const [deptPerms] = await db.query(
|
|
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
|
|
[ssoUser.department_id]
|
|
);
|
|
deptPerms.forEach(p => { deptPermMap[p.page_name] = !!p.can_access; });
|
|
}
|
|
|
|
// 3) 페이지 목록 조회 + 권한 매핑
|
|
const [pages] = await db.query(`
|
|
SELECT id, page_key, page_name, page_path, category, is_admin_only
|
|
FROM pages
|
|
WHERE is_admin_only = 0
|
|
ORDER BY display_order, page_name
|
|
`);
|
|
|
|
const pageAccess = pages.map(page => {
|
|
const tkuserKey = PAGEKEY_TO_TKUSER[page.page_key];
|
|
let canAccess = false;
|
|
|
|
if (tkuserKey) {
|
|
// 우선순위: 개인 권한 > 부서 권한 > default_access
|
|
if (tkuserKey in userPermMap) {
|
|
canAccess = userPermMap[tkuserKey];
|
|
} else if (tkuserKey in deptPermMap) {
|
|
canAccess = deptPermMap[tkuserKey];
|
|
} else if (tkuserKey in TKUSER_DEFAULT_ACCESS) {
|
|
canAccess = TKUSER_DEFAULT_ACCESS[tkuserKey];
|
|
}
|
|
}
|
|
|
|
return {
|
|
page_id: page.id,
|
|
page_key: page.page_key,
|
|
page_name: page.page_name,
|
|
page_path: page.page_path,
|
|
category: page.category,
|
|
is_admin_only: page.is_admin_only,
|
|
can_access: canAccess ? 1 : 0
|
|
};
|
|
});
|
|
|
|
res.json({ success: true, data: { user: ssoUser, pageAccess } });
|
|
} catch (error) {
|
|
console.error('페이지 접근 권한 조회 오류:', error);
|
|
res.status(500).json({ success: false, error: '페이지 접근 권한을 불러오는데 실패했습니다.' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자에게 페이지 접근 권한 부여/회수
|
|
* POST /api/users/:userId/page-access
|
|
* Body: { pageIds: [1, 2, 3], canAccess: true }
|
|
*/
|
|
router.post('/users/:userId/page-access', requireAuth, async (req, res) => {
|
|
try {
|
|
const { userId } = req.params;
|
|
const { pageIds, canAccess } = req.body;
|
|
const adminUserId = req.user.user_id; // 권한을 부여하는 Admin의 user_id
|
|
|
|
// Admin 권한 확인
|
|
const db = await getDb();
|
|
const [adminRows] = await db.query(`
|
|
SELECT u.role_id, r.name as role_name
|
|
FROM users u
|
|
LEFT JOIN roles r ON u.role_id = r.id
|
|
WHERE u.user_id = ?
|
|
`, [adminUserId]);
|
|
|
|
if (adminRows.length === 0 || (adminRows[0].role_name !== 'Admin' && adminRows[0].role_name !== 'System Admin')) {
|
|
return res.status(403).json({ success: false, error: '권한이 없습니다. Admin 계정만 사용자 권한을 관리할 수 있습니다.' });
|
|
}
|
|
|
|
// 사용자 존재 확인
|
|
const [userRows] = await db.query('SELECT user_id FROM users WHERE user_id = ?', [userId]);
|
|
if (userRows.length === 0) {
|
|
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 페이지 접근 권한 업데이트
|
|
for (const pageId of pageIds) {
|
|
// 기존 권한 확인
|
|
const [existing] = await db.query(
|
|
'SELECT * FROM user_page_access WHERE user_id = ? AND page_id = ?',
|
|
[userId, pageId]
|
|
);
|
|
|
|
if (existing.length > 0) {
|
|
// 업데이트
|
|
await db.query(
|
|
'UPDATE user_page_access SET can_access = ?, granted_at = NOW(), granted_by = ? WHERE user_id = ? AND page_id = ?',
|
|
[canAccess ? 1 : 0, adminUserId, userId, pageId]
|
|
);
|
|
} else {
|
|
// 삽입
|
|
await db.query(
|
|
'INSERT INTO user_page_access (user_id, page_id, can_access, granted_by) VALUES (?, ?, ?, ?)',
|
|
[userId, pageId, canAccess ? 1 : 0, adminUserId]
|
|
);
|
|
}
|
|
}
|
|
|
|
res.json({ success: true, message: '페이지 접근 권한이 업데이트되었습니다.' });
|
|
} catch (error) {
|
|
console.error('페이지 접근 권한 부여 오류:', error);
|
|
res.status(500).json({ success: false, error: '페이지 접근 권한을 업데이트하는데 실패했습니다.' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 특정 페이지의 접근 권한 회수
|
|
* DELETE /api/users/:userId/page-access/:pageId
|
|
*/
|
|
router.delete('/users/:userId/page-access/:pageId', requireAuth, async (req, res) => {
|
|
try {
|
|
const { userId, pageId } = req.params;
|
|
const adminUserId = req.user.user_id;
|
|
|
|
// Admin 권한 확인
|
|
const db = await getDb();
|
|
const [adminRows] = await db.query(`
|
|
SELECT u.role_id, r.name as role_name
|
|
FROM users u
|
|
LEFT JOIN roles r ON u.role_id = r.id
|
|
WHERE u.user_id = ?
|
|
`, [adminUserId]);
|
|
|
|
if (adminRows.length === 0 || (adminRows[0].role_name !== 'Admin' && adminRows[0].role_name !== 'System Admin')) {
|
|
return res.status(403).json({ success: false, error: '권한이 없습니다.' });
|
|
}
|
|
|
|
// 접근 권한 삭제
|
|
await db.query(
|
|
'DELETE FROM user_page_access WHERE user_id = ? AND page_id = ?',
|
|
[userId, pageId]
|
|
);
|
|
|
|
res.json({ success: true, message: '페이지 접근 권한이 회수되었습니다.' });
|
|
} catch (error) {
|
|
console.error('페이지 접근 권한 회수 오류:', error);
|
|
res.status(500).json({ success: false, error: '페이지 접근 권한을 회수하는데 실패했습니다.' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 모든 사용자의 페이지 접근 권한 요약 조회 (Admin용)
|
|
* GET /api/page-access/summary
|
|
*/
|
|
router.get('/page-access/summary', requireAuth, async (req, res) => {
|
|
try {
|
|
const adminUserId = req.user.user_id;
|
|
|
|
// Admin 권한 확인
|
|
const db = await getDb();
|
|
const [adminRows] = await db.query(`
|
|
SELECT u.role_id, r.name as role_name
|
|
FROM users u
|
|
LEFT JOIN roles r ON u.role_id = r.id
|
|
WHERE u.user_id = ?
|
|
`, [adminUserId]);
|
|
|
|
if (adminRows.length === 0 || (adminRows[0].role_name !== 'Admin' && adminRows[0].role_name !== 'System Admin')) {
|
|
return res.status(403).json({ success: false, error: '권한이 없습니다.' });
|
|
}
|
|
|
|
// 모든 사용자와 페이지 권한 조회
|
|
const [summary] = await db.query(`
|
|
SELECT
|
|
u.user_id,
|
|
u.username,
|
|
u.name,
|
|
r.name as role_name,
|
|
COUNT(DISTINCT upa.page_id) as accessible_pages_count,
|
|
(SELECT COUNT(*) FROM pages WHERE is_admin_only = 0) as total_pages_count
|
|
FROM users u
|
|
LEFT JOIN roles r ON u.role_id = r.id
|
|
LEFT JOIN user_page_access upa ON u.user_id = upa.user_id AND upa.can_access = 1
|
|
WHERE r.name NOT IN ('Admin', 'System Admin')
|
|
GROUP BY u.user_id, u.username, u.name, r.name
|
|
ORDER BY u.username
|
|
`);
|
|
|
|
res.json({ success: true, data: summary });
|
|
} catch (error) {
|
|
console.error('페이지 접근 권한 요약 조회 오류:', error);
|
|
res.status(500).json({ success: false, error: '페이지 접근 권한 요약을 불러오는데 실패했습니다.' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|