feat(tkuser): 권한 관리 페이지 최신화 — tksupport 추가, tksafety 보강, S1 휴가 정리

- tksupport 행정지원 6페이지 권한 정의 추가 (indigo 테마)
- tksupport 라우트에 requirePage() 미들웨어 적용
- tksafety 권한 2→8개 확장 (출입관리 4 + 교육/점검 4)
- System1 안전관리 그룹 제거 (s1.safety.* 고아키)
- System1 근태관리 휴가 5항목 제거 (tksupport로 통합)
- 월간근태를 공장관리 그룹으로 이동
- System3 업무, tkuser 연차설정 백엔드 키 동기화

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-25 14:06:06 +09:00
parent d46e509e42
commit 05c9f22bdf
5 changed files with 69 additions and 45 deletions

View File

@@ -1,13 +1,13 @@
const express = require('express');
const router = express.Router();
const { requireAuth, requireSupportTeam } = require('../middleware/auth');
const { requireAuth, requireSupportTeam, requirePage } = 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);
router.post('/holidays', requireSupportTeam, requirePage('support_company_holidays'), ctrl.createHoliday);
router.delete('/holidays/:id', requireSupportTeam, requirePage('support_company_holidays'), ctrl.deleteHoliday);
router.post('/holidays/:id/apply-deduction', requireSupportTeam, requirePage('support_company_holidays'), ctrl.applyDeduction);
module.exports = router;

View File

@@ -1,12 +1,12 @@
const express = require('express');
const router = express.Router();
const { requireAuth, requireSupportTeam } = require('../middleware/auth');
const { requireAuth, requireSupportTeam, requirePage } = require('../middleware/auth');
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);
router.get('/', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getDashboard);
router.get('/yearly-overview', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getYearlyOverview);
router.get('/monthly-detail', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getMonthlyDetail);
module.exports = router;

View File

@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
const { requireAuth, requireAdmin } = require('../middleware/auth');
const { requireAuth, requireAdmin, requirePage } = require('../middleware/auth');
const ctrl = require('../controllers/vacationController');
router.use(requireAuth);
@@ -9,16 +9,16 @@ router.use(requireAuth);
router.get('/types', ctrl.getVacationTypes);
// 휴가 신청
router.post('/requests', ctrl.createRequest);
router.post('/requests', requirePage('support_vacation_request'), ctrl.createRequest);
router.get('/requests', ctrl.getRequests);
router.get('/requests/:id', ctrl.getRequestById);
router.put('/requests/:id', ctrl.updateRequest);
router.patch('/requests/:id/cancel', ctrl.cancelRequest);
router.put('/requests/:id', requirePage('support_vacation_request'), ctrl.updateRequest);
router.patch('/requests/:id/cancel', requirePage('support_vacation_request'), ctrl.cancelRequest);
// 승인 (관리자)
router.get('/pending', requireAdmin, ctrl.getPending);
router.patch('/requests/:id/approve', requireAdmin, ctrl.approveRequest);
router.patch('/requests/:id/reject', requireAdmin, ctrl.rejectRequest);
router.patch('/requests/:id/approve', requireAdmin, requirePage('support_vacation_approval'), ctrl.approveRequest);
router.patch('/requests/:id/reject', requireAdmin, requirePage('support_vacation_approval'), ctrl.rejectRequest);
// 내 휴가 현황
router.get('/my-status', ctrl.getMyStatus);

View File

@@ -20,13 +20,7 @@ const DEFAULT_PAGES = {
's1.inspection.daily_patrol': { title: '일일순회점검', system: 'system1', group: '공장 관리', default_access: false },
's1.inspection.checkin': { title: '출근 체크', system: 'system1', group: '공장 관리', default_access: true },
's1.inspection.work_status': { title: '근무 현황', system: 'system1', group: '공장 관리', default_access: false },
// 근태 관리
's1.attendance.my_vacation_info': { title: '내 연차 정보', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.monthly': { title: '월간 근태', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.vacation_request': { title: '휴가 신청', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.vacation_management': { title: '휴가 관리', system: 'system1', group: '근태 관리', default_access: false },
's1.attendance.vacation_allocation': { title: '휴가 발생 입력', system: 'system1', group: '근태 관리', default_access: false },
's1.attendance.annual_overview': { title: '연간 휴가 현황', system: 'system1', group: '근태 관리', default_access: false },
's1.attendance.monthly': { title: '월간 근태', system: 'system1', group: '공장 관리', default_access: true },
// 시스템 관리
's1.admin.workers': { title: '작업자 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.projects': { title: '프로젝트 관리', system: 'system1', group: '시스템 관리', default_access: false },
@@ -47,6 +41,9 @@ const DEFAULT_PAGES = {
'reports_daily': { title: '일일보고서', system: 'system3', group: '보고서', default_access: false },
'reports_weekly': { title: '주간보고서', system: 'system3', group: '보고서', default_access: false },
'reports_monthly': { title: '월간보고서', system: 'system3', group: '보고서', default_access: false },
// 업무
'daily_work': { title: '일일 공수', system: 'system3', group: '업무', default_access: false },
'projects_manage': { title: '프로젝트 관리', system: 'system3', group: '업무', default_access: false },
// AI
'ai_assistant': { title: 'AI 어시스턴트', system: 'system3', group: 'AI', default_access: false },
@@ -59,11 +56,26 @@ const DEFAULT_PAGES = {
'purchasing_partner_checkin': { title: '협력업체 체크인', system: 'tkpurchase', group: '협력업체', default_access: false },
// ===== tksafety - 안전 관리 =====
'safety_visit_request': { title: '출입 신청', system: 'tksafety', group: '안전 관리', default_access: true },
'safety_visit_management': { title: '출입 관리', system: 'tksafety', group: '안전 관리', default_access: false },
'safety_training': { title: '안전교육 실시', system: 'tksafety', group: '안전 관리', default_access: false },
'safety_checklist': { title: '체크리스트 관리', system: 'tksafety', group: '안전 관리', default_access: false },
'safety_entry_dashboard': { title: '출입 현황판', system: 'tksafety', group: '안전 관리', default_access: false },
// 출입 관리
'safety_visit': { title: '방문 관리', system: 'tksafety', group: '출입 관리', default_access: true },
'safety_visit_request': { title: '출입 신청', system: 'tksafety', group: '출입 관리', default_access: true },
'safety_visit_management': { title: '출입 승인', system: 'tksafety', group: '출입 관리', default_access: false },
'safety_entry_dashboard': { title: '출입 현황판', system: 'tksafety', group: '출입 관리', default_access: false },
// 교육/점검
'safety_education': { title: '안전교육', system: 'tksafety', group: '교육/점검', default_access: true },
'safety_training': { title: '안전교육 실시', system: 'tksafety', group: '교육/점검', default_access: false },
'safety_risk_assessment': { title: '위험성평가', system: 'tksafety', group: '교육/점검', default_access: false },
'safety_checklist': { title: '체크리스트 관리', system: 'tksafety', group: '교육/점검', default_access: false },
// ===== tksupport - 행정 지원 =====
// 일반
'support_dashboard': { title: '대시보드', system: 'tksupport', group: '일반', default_access: true },
'support_vacation_request': { title: '휴가 신청', system: 'tksupport', group: '일반', default_access: true },
'support_vacation_status': { title: '내 휴가 현황', system: 'tksupport', group: '일반', default_access: true },
// 관리
'support_vacation_approval': { title: '휴가 승인', system: 'tksupport', group: '관리', default_access: false },
'support_company_holidays': { title: '전사 휴가 관리', system: 'tksupport', group: '관리', default_access: false },
'support_vacation_dashboard': { title: '전체 휴가관리', system: 'tksupport', group: '관리', default_access: false },
// ===== tkuser - 통합 관리 =====
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
@@ -74,6 +86,7 @@ const DEFAULT_PAGES = {
'tkuser.issue_types': { title: '이슈 유형 관리', system: 'tkuser', group: '통합 관리', default_access: false },
'tkuser.tasks': { title: '작업 관리', system: 'tkuser', group: '통합 관리', default_access: false },
'tkuser.vacations': { title: '휴가 관리', system: 'tkuser', group: '통합 관리', default_access: false },
'tkuser.vacation_settings': { title: '연차 설정', system: 'tkuser', group: '통합 관리', default_access: false },
'tkuser.partners': { title: '협력업체 관리', system: 'tkuser', group: '통합 관리', default_access: false },
'tkuser.notification_recipients': { title: '알림 수신자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
};

View File

@@ -12,19 +12,7 @@ const SYSTEM1_PAGES = {
{ key: 's1.inspection.daily_patrol', title: '일일순회점검', icon: 'fa-clipboard-check', def: false },
{ key: 's1.inspection.checkin', title: '출근 체크', icon: 'fa-fingerprint', def: true },
{ key: 's1.inspection.work_status', title: '근무 현황', icon: 'fa-user-clock', def: false },
],
'안전 관리': [
{ key: 's1.safety.visit_request', title: '출입 신청', icon: 'fa-id-badge', def: true },
{ key: 's1.safety.management', title: '안전 관리', icon: 'fa-fire-extinguisher', def: false },
{ key: 's1.safety.checklist_manage', title: '체크리스트 관리', icon: 'fa-list-check', def: false },
],
'근태 관리': [
{ key: 's1.attendance.my_vacation_info', title: '내 연차 정보', icon: 'fa-umbrella-beach', def: true },
{ key: 's1.attendance.monthly', title: '월간 근태', icon: 'fa-calendar-days', def: true },
{ key: 's1.attendance.vacation_request', title: '휴가 신청', icon: 'fa-paper-plane', def: true },
{ key: 's1.attendance.vacation_management', title: '휴가 관리', icon: 'fa-calendar-check', def: false },
{ key: 's1.attendance.vacation_allocation', title: '휴가 발생 입력', icon: 'fa-calendar-plus', def: false },
{ key: 's1.attendance.annual_overview', title: '연간 휴가 현황', icon: 'fa-chart-pie', def: false },
],
'시스템 관리': [
{ key: 's1.admin.workers', title: '작업자 관리', icon: 'fa-people-group', def: false },
@@ -73,9 +61,30 @@ const TKPURCHASE_PAGES = {
};
const TKSAFETY_PAGES = {
'안전 관리': [
{ key: 'safety_visit', title: '방문 관리', icon: 'fa-door-open', def: false },
{ key: 'safety_education', title: '안전교육 관리', icon: 'fa-graduation-cap', def: false },
'출입 관리': [
{ key: 'safety_visit', title: '방문 관리', icon: 'fa-door-open', def: true },
{ key: 'safety_visit_request', title: '출입 신청', icon: 'fa-file-signature', def: true },
{ key: 'safety_visit_management', title: '출입 승인', icon: 'fa-clipboard-check', def: false },
{ key: 'safety_entry_dashboard', title: '출입 현황판', icon: 'fa-tv', def: false },
],
'교육/점검': [
{ key: 'safety_education', title: '안전교육', icon: 'fa-graduation-cap', def: true },
{ key: 'safety_training', title: '안전교육 실시', icon: 'fa-chalkboard-teacher', def: false },
{ key: 'safety_risk_assessment', title: '위험성평가', icon: 'fa-exclamation-triangle', def: false },
{ key: 'safety_checklist', title: '체크리스트 관리', icon: 'fa-tasks', def: false },
]
};
const TKSUPPORT_PAGES = {
'일반': [
{ key: 'support_dashboard', title: '대시보드', icon: 'fa-home', def: true },
{ key: 'support_vacation_request', title: '휴가 신청', icon: 'fa-paper-plane', def: true },
{ key: 'support_vacation_status', title: '내 휴가 현황', icon: 'fa-calendar-check', def: true },
],
'관리': [
{ key: 'support_vacation_approval', title: '휴가 승인', icon: 'fa-user-check', def: false },
{ key: 'support_company_holidays', title: '전사 휴가 관리', icon: 'fa-calendar-day', def: false },
{ key: 'support_vacation_dashboard', title: '전체 휴가관리', icon: 'fa-chart-bar', def: false },
]
};
@@ -301,7 +310,7 @@ async function loadUserPermissions(userId) {
currentPermissions = {};
currentPermSources = {};
currentDeptGranted = {};
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKUSER_PAGES };
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKSUPPORT_PAGES, ...TKUSER_PAGES };
Object.values(allDefs).flat().forEach(p => { currentPermissions[p.key] = p.def; currentPermSources[p.key] = 'default'; currentDeptGranted[p.key] = false; });
try {
const result = await api(`/permissions/users/${userId}/effective-permissions`);
@@ -320,6 +329,7 @@ function renderPermissionGrid() {
renderSystemPerms('s3-perms', SYSTEM3_PAGES, 'purple');
renderSystemPerms('tkpurchase-perms', TKPURCHASE_PAGES, 'green');
renderSystemPerms('tksafety-perms', TKSAFETY_PAGES, 'orange');
renderSystemPerms('tksupport-perms', TKSUPPORT_PAGES, 'indigo');
renderSystemPerms('tkuser-perms', TKUSER_PAGES, 'slate');
}
@@ -427,7 +437,7 @@ document.getElementById('savePermissionsBtn').addEventListener('click', async ()
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>저장 중...';
try {
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKSUPPORT_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
const permissions = allPages
.filter(p => !currentDeptGranted[p.key])
.map(p => {
@@ -487,7 +497,7 @@ document.addEventListener('DOMContentLoaded', () => {
async function loadDeptPermissions(deptId) {
deptPermissions = {};
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKUSER_PAGES };
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKSUPPORT_PAGES, ...TKUSER_PAGES };
Object.values(allDefs).flat().forEach(p => { deptPermissions[p.key] = p.def; });
try {
const result = await api(`/permissions/departments/${deptId}/permissions`);
@@ -500,6 +510,7 @@ function renderDeptPermissionGrid() {
renderDeptSystemPerms('dept-s3-perms', SYSTEM3_PAGES, 'purple');
renderDeptSystemPerms('dept-tkpurchase-perms', TKPURCHASE_PAGES, 'green');
renderDeptSystemPerms('dept-tksafety-perms', TKSAFETY_PAGES, 'orange');
renderDeptSystemPerms('dept-tksupport-perms', TKSUPPORT_PAGES, 'indigo');
renderDeptSystemPerms('dept-tkuser-perms', TKUSER_PAGES, 'slate');
}
@@ -579,7 +590,7 @@ async function saveDeptPermissions() {
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>저장 중...';
try {
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKSUPPORT_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
const permissions = allPages.map(p => {
const cb = document.getElementById('dperm_' + p.key);
return { page_name: p.key, can_access: cb ? cb.checked : false };