From 05c9f22bdf36983f001e6127950e40914b773d74 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 25 Mar 2026 14:06:06 +0900 Subject: [PATCH] =?UTF-8?q?feat(tkuser):=20=EA=B6=8C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94=20=E2=80=94=20tksupport=20=EC=B6=94=EA=B0=80,=20tksaf?= =?UTF-8?q?ety=20=EB=B3=B4=EA=B0=95,=20S1=20=ED=9C=B4=EA=B0=80=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- tksupport/api/routes/companyHolidayRoutes.js | 8 +-- .../api/routes/vacationDashboardRoutes.js | 8 +-- tksupport/api/routes/vacationRoutes.js | 12 ++--- user-management/api/models/permissionModel.js | 37 +++++++++----- user-management/web/static/js/tkuser-users.js | 49 ++++++++++++------- 5 files changed, 69 insertions(+), 45 deletions(-) diff --git a/tksupport/api/routes/companyHolidayRoutes.js b/tksupport/api/routes/companyHolidayRoutes.js index 6e5bb8a..d70a9cb 100644 --- a/tksupport/api/routes/companyHolidayRoutes.js +++ b/tksupport/api/routes/companyHolidayRoutes.js @@ -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; diff --git a/tksupport/api/routes/vacationDashboardRoutes.js b/tksupport/api/routes/vacationDashboardRoutes.js index 5f9f443..ce84e5b 100644 --- a/tksupport/api/routes/vacationDashboardRoutes.js +++ b/tksupport/api/routes/vacationDashboardRoutes.js @@ -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; diff --git a/tksupport/api/routes/vacationRoutes.js b/tksupport/api/routes/vacationRoutes.js index 194136c..fca3432 100644 --- a/tksupport/api/routes/vacationRoutes.js +++ b/tksupport/api/routes/vacationRoutes.js @@ -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); diff --git a/user-management/api/models/permissionModel.js b/user-management/api/models/permissionModel.js index 4f22777..afc5f6b 100644 --- a/user-management/api/models/permissionModel.js +++ b/user-management/api/models/permissionModel.js @@ -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 }, }; diff --git a/user-management/web/static/js/tkuser-users.js b/user-management/web/static/js/tkuser-users.js index 54b5792..00ae986 100644 --- a/user-management/web/static/js/tkuser-users.js +++ b/user-management/web/static/js/tkuser-users.js @@ -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 = '저장 중...'; 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 = '저장 중...'; 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 };