Section A (Backend): - POST /api/proxy-input: TBM 세션+팀배정+작업보고서 일괄 생성 (트랜잭션) - GET /api/proxy-input/daily-status: 일별 TBM/보고서 입력 현황 - GET /api/proxy-input/daily-status/detail: 작업자별 상세 - tbm_sessions에 is_proxy_input, proxy_input_by 컬럼 추가 - system1/system2/tkuser requireMinLevel → shared requirePage 전환 - permissionModel에 factory_proxy_input, factory_daily_status 키 등록 Section B (Frontend): - daily-status.html: 날짜 네비 + 요약 카드 + 필터 탭 + 작업자 리스트 + 바텀시트 - proxy-input.html: 미입력자 카드 + 확장 폼 + 일괄 설정 + 저장 - tkfb-core.js NAV_MENU에 입력 현황/대리입력 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
371 lines
18 KiB
JavaScript
371 lines
18 KiB
JavaScript
/**
|
|
* Permission Model
|
|
*
|
|
* MariaDB user_page_permissions 테이블 CRUD
|
|
*/
|
|
|
|
const { getPool } = require('./userModel');
|
|
|
|
// 기본 페이지 목록 (시스템별 구분)
|
|
const DEFAULT_PAGES = {
|
|
// ===== System 1 - 공장관리 =====
|
|
// 작업 관리
|
|
's1.dashboard': { title: '대시보드', system: 'system1', group: '작업 관리', default_access: true },
|
|
's1.work.tbm': { title: 'TBM 관리', system: 'system1', group: '작업 관리', default_access: true },
|
|
's1.work.report_create': { title: '작업보고서 작성', system: 'system1', group: '작업 관리', default_access: true },
|
|
's1.work.analysis': { title: '작업 분석', system: 'system1', group: '작업 관리', default_access: false },
|
|
's1.work.nonconformity': { title: '부적합 현황', system: 'system1', group: '작업 관리', default_access: true },
|
|
// 공장 관리
|
|
's1.factory.repair_management':{ title: '시설설비 관리', system: 'system1', group: '공장 관리', default_access: false },
|
|
'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.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 },
|
|
's1.admin.tasks': { title: '작업 관리', system: 'system1', group: '시스템 관리', default_access: false },
|
|
's1.admin.workplaces': { title: '작업장 관리', system: 'system1', group: '시스템 관리', default_access: false },
|
|
's1.admin.equipments': { title: '설비 관리', system: 'system1', group: '시스템 관리', default_access: false },
|
|
's1.admin.issue_categories': { title: '신고 카테고리 관리', system: 'system1', group: '시스템 관리', default_access: false },
|
|
's1.admin.attendance_report': { title: '출퇴근-보고서 대조', system: 'system1', group: '시스템 관리', default_access: false },
|
|
// 관리
|
|
'factory_proxy_input': { title: '대리입력', system: 'system1', group: '관리', default_access: false },
|
|
'factory_daily_status': { title: '일별 현황', system: 'system1', group: '관리', default_access: false },
|
|
'factory_tbm': { title: 'TBM 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_work_report': { title: '작업보고서', system: 'system1', group: '관리', default_access: false },
|
|
'factory_projects': { title: '프로젝트 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_work_issues': { title: '업무 이슈', system: 'system1', group: '관리', default_access: false },
|
|
'factory_purchases': { title: '구매 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_schedules': { title: '일정 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_settlements': { title: '정산 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_meetings': { title: '회의 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_departments': { title: '부서 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_tools': { title: '도구 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_uploads': { title: '업로드 관리', system: 'system1', group: '관리', default_access: false },
|
|
'factory_work_analysis': { title: '공수 분석', system: 'system1', group: '관리', default_access: false },
|
|
'factory_system': { title: '시스템 관리', system: 'system1', group: '시스템 관리', default_access: false },
|
|
// system2 report
|
|
'report_work_issues': { title: '업무 이슈', system: 'system2', group: '관리', default_access: false },
|
|
// tkpurchase
|
|
'purchase_schedules': { title: '작업일정', system: 'tkpurchase', group: '관리', default_access: false },
|
|
|
|
// ===== System 3 - 부적합관리 =====
|
|
// 메인
|
|
'issues_dashboard': { title: '현황판', system: 'system3', group: '메인', default_access: true },
|
|
'issues_inbox': { title: '수신함', system: 'system3', group: '메인', default_access: true },
|
|
'issues_management': { title: '관리함', system: 'system3', group: '메인', default_access: false },
|
|
'issues_archive': { title: '폐기함', system: 'system3', group: '메인', default_access: false },
|
|
// 보고서
|
|
'reports': { title: '보고서', system: 'system3', group: '보고서', default_access: false },
|
|
'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 },
|
|
|
|
// ===== tkpurchase - 구매 관리 =====
|
|
'purchasing_daylabor': { title: '일용공 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
|
'purchasing_schedule': { title: '작업일정 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
|
'purchasing_workreport': { title: '업무현황 관리', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
|
'purchasing_accounts': { title: '협력업체 계정', system: 'tkpurchase', group: '구매 관리', default_access: false },
|
|
'purchasing_partner_portal': { title: '협력업체 포털', system: 'tkpurchase', group: '협력업체', default_access: false },
|
|
'purchasing_partner_checkin': { title: '협력업체 체크인', system: 'tkpurchase', group: '협력업체', default_access: false },
|
|
|
|
// ===== tksafety - 안전 관리 =====
|
|
// 출입 관리
|
|
'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 },
|
|
'support_vacation_admin': { title: '휴가 보정', system: 'tksupport', group: '관리', default_access: false },
|
|
|
|
// ===== tkuser - 통합 관리 =====
|
|
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
|
'tkuser.projects': { title: '프로젝트 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
|
'tkuser.workplaces': { title: '작업장 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
|
'tkuser.workers': { title: '작업자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
|
'tkuser.departments': { title: '부서 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
|
'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 },
|
|
};
|
|
|
|
/**
|
|
* 사용자의 페이지 권한 목록 조회
|
|
*/
|
|
async function getUserPermissions(userId) {
|
|
const db = getPool();
|
|
const [rows] = await db.query(
|
|
'SELECT * FROM user_page_permissions WHERE user_id = ? ORDER BY page_name',
|
|
[userId]
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* 단건 권한 부여/업데이트 (UPSERT)
|
|
*/
|
|
async function grantPermission({ user_id, page_name, can_access, granted_by_id, notes }) {
|
|
const db = getPool();
|
|
const [result] = await db.query(
|
|
`INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id, notes)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), notes = VALUES(notes), granted_at = CURRENT_TIMESTAMP`,
|
|
[user_id, page_name, can_access, granted_by_id, notes || null]
|
|
);
|
|
return { id: result.insertId, user_id, page_name, can_access };
|
|
}
|
|
|
|
/**
|
|
* 일괄 권한 부여 (부서 허용 페이지는 스킵 + 기존 중복 레코드 정리)
|
|
*/
|
|
async function bulkGrant({ user_id, permissions, granted_by_id }) {
|
|
const db = getPool();
|
|
let count = 0;
|
|
|
|
// 부서 권한 조회
|
|
const [userRows] = await db.query(
|
|
'SELECT department_id FROM sso_users WHERE user_id = ?', [user_id]
|
|
);
|
|
const deptId = userRows.length > 0 ? userRows[0].department_id : null;
|
|
|
|
const deptGranted = {};
|
|
if (deptId) {
|
|
const [deptPerms] = await db.query(
|
|
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
|
|
[deptId]
|
|
);
|
|
deptPerms.forEach(p => { if (p.can_access) deptGranted[p.page_name] = true; });
|
|
}
|
|
|
|
for (const perm of permissions) {
|
|
if (!DEFAULT_PAGES[perm.page_name]) continue;
|
|
|
|
// 부서가 허용한 페이지는 개인 레코드 삭제 (스킵)
|
|
if (deptGranted[perm.page_name]) {
|
|
await db.query(
|
|
'DELETE FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
|
|
[user_id, perm.page_name]
|
|
);
|
|
continue;
|
|
}
|
|
|
|
await db.query(
|
|
`INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
|
|
[user_id, perm.page_name, perm.can_access, granted_by_id]
|
|
);
|
|
count++;
|
|
}
|
|
|
|
return { updated_count: count };
|
|
}
|
|
|
|
/**
|
|
* 접근 권한 확인 (우선순위: 부서 OR 개인 OR 기본값)
|
|
* 부서가 허용하면 무조건 허용 (개인이 해제 불가)
|
|
*/
|
|
async function checkAccess(userId, pageName) {
|
|
const db = getPool();
|
|
|
|
// 1. 부서 권한 (마스터) — 부서가 허용하면 무조건 허용
|
|
const [userRows] = await db.query(
|
|
'SELECT department_id FROM sso_users WHERE user_id = ?', [userId]
|
|
);
|
|
if (userRows.length > 0 && userRows[0].department_id) {
|
|
const [deptRows] = await db.query(
|
|
'SELECT can_access FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
|
|
[userRows[0].department_id, pageName]
|
|
);
|
|
if (deptRows.length > 0 && deptRows[0].can_access) {
|
|
return { can_access: true, reason: 'department_permission' };
|
|
}
|
|
}
|
|
|
|
// 2. 개인 권한 (추가 부여)
|
|
const [rows] = await db.query(
|
|
'SELECT can_access FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
|
|
[userId, pageName]
|
|
);
|
|
if (rows.length > 0) {
|
|
return { can_access: rows[0].can_access, reason: 'explicit_permission' };
|
|
}
|
|
|
|
// 3. 기본 권한
|
|
const pageConfig = DEFAULT_PAGES[pageName];
|
|
if (!pageConfig) return { can_access: false, reason: 'invalid_page' };
|
|
return { can_access: pageConfig.default_access, reason: 'default_permission' };
|
|
}
|
|
|
|
/**
|
|
* 부서별 페이지 권한 조회
|
|
*/
|
|
async function getDepartmentPermissions(departmentId) {
|
|
const db = getPool();
|
|
const [rows] = await db.query(
|
|
'SELECT * FROM department_page_permissions WHERE department_id = ? ORDER BY page_name',
|
|
[departmentId]
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* 부서 권한 단건 UPSERT
|
|
*/
|
|
async function setDepartmentPermission({ department_id, page_name, can_access, granted_by_id }) {
|
|
const db = getPool();
|
|
const [result] = await db.query(
|
|
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
|
|
[department_id, page_name, can_access, granted_by_id]
|
|
);
|
|
return { id: result.insertId, department_id, page_name, can_access };
|
|
}
|
|
|
|
/**
|
|
* 부서 권한 일괄 설정
|
|
*/
|
|
async function bulkSetDepartmentPermissions({ department_id, permissions, granted_by_id }) {
|
|
const db = getPool();
|
|
let count = 0;
|
|
|
|
for (const perm of permissions) {
|
|
if (!DEFAULT_PAGES[perm.page_name]) continue;
|
|
await db.query(
|
|
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
|
|
[department_id, perm.page_name, perm.can_access, granted_by_id]
|
|
);
|
|
count++;
|
|
}
|
|
|
|
return { updated_count: count };
|
|
}
|
|
|
|
/**
|
|
* 부서 권한 삭제 (기본값으로 복귀)
|
|
*/
|
|
async function deleteDepartmentPermission(departmentId, pageName) {
|
|
const db = getPool();
|
|
const [result] = await db.query(
|
|
'DELETE FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
|
|
[departmentId, pageName]
|
|
);
|
|
return result.affectedRows > 0;
|
|
}
|
|
|
|
/**
|
|
* 사용자 권한 + 출처 조회 (각 페이지별 현재 적용 권한과 출처 반환)
|
|
*/
|
|
async function getUserPermissionsWithSource(userId) {
|
|
const db = getPool();
|
|
|
|
// 개인 권한 조회
|
|
const [userPerms] = await db.query(
|
|
'SELECT page_name, can_access FROM user_page_permissions WHERE user_id = ?',
|
|
[userId]
|
|
);
|
|
const userPermMap = {};
|
|
userPerms.forEach(p => { userPermMap[p.page_name] = !!p.can_access; });
|
|
|
|
// 부서 권한 조회
|
|
const [userRows] = await db.query(
|
|
'SELECT department_id FROM sso_users WHERE user_id = ?',
|
|
[userId]
|
|
);
|
|
const deptId = userRows.length > 0 ? userRows[0].department_id : null;
|
|
|
|
const deptPermMap = {};
|
|
if (deptId) {
|
|
const [deptPerms] = await db.query(
|
|
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
|
|
[deptId]
|
|
);
|
|
deptPerms.forEach(p => { deptPermMap[p.page_name] = !!p.can_access; });
|
|
}
|
|
|
|
// 모든 페이지에 대해 결과 조합 (부서 OR 개인 OR 기본)
|
|
const result = {};
|
|
for (const [pageName, config] of Object.entries(DEFAULT_PAGES)) {
|
|
const deptGranted = deptPermMap[pageName] === true;
|
|
|
|
if (deptGranted) {
|
|
// 부서가 허용 → 무조건 허용, 잠금
|
|
result[pageName] = { can_access: true, source: 'department', dept_granted: true };
|
|
} else if (pageName in userPermMap) {
|
|
result[pageName] = { can_access: userPermMap[pageName], source: 'explicit', dept_granted: false };
|
|
} else {
|
|
result[pageName] = { can_access: config.default_access, source: 'default', dept_granted: false };
|
|
}
|
|
}
|
|
|
|
return { permissions: result, department_id: deptId };
|
|
}
|
|
|
|
/**
|
|
* 부서 이동 시 개인 권한 초기화 — user_page_permissions 전체 삭제
|
|
*/
|
|
async function clearUserPermissionsForDepartmentChange(userId) {
|
|
const db = getPool();
|
|
const [result] = await db.query(
|
|
'DELETE FROM user_page_permissions WHERE user_id = ?',
|
|
[userId]
|
|
);
|
|
return { deleted_count: result.affectedRows };
|
|
}
|
|
|
|
/**
|
|
* 권한 삭제 (기본값으로 되돌림)
|
|
*/
|
|
async function deletePermission(permissionId) {
|
|
const db = getPool();
|
|
const [rows] = await db.query(
|
|
'SELECT * FROM user_page_permissions WHERE id = ?',
|
|
[permissionId]
|
|
);
|
|
if (rows.length === 0) return null;
|
|
|
|
await db.query('DELETE FROM user_page_permissions WHERE id = ?', [permissionId]);
|
|
return rows[0];
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_PAGES,
|
|
getUserPermissions,
|
|
grantPermission,
|
|
bulkGrant,
|
|
checkAccess,
|
|
deletePermission,
|
|
getDepartmentPermissions,
|
|
setDepartmentPermission,
|
|
bulkSetDepartmentPermissions,
|
|
deleteDepartmentPermission,
|
|
getUserPermissionsWithSource,
|
|
clearUserPermissionsForDepartmentChange
|
|
};
|