feat: 구매/안전 시스템 전면 개편 — tkpurchase 개편 + tksafety 신규 + 권한 보강

Phase 1: tkuser 협력업체 CRUD 이관 (읽기전용 → 전체 CRUD)
Phase 2: tkpurchase 개편 — 일용공 신청/확정, 작업일정, 업무현황, 계정관리, 협력업체 포털
Phase 3: tksafety 신규 시스템 — 방문관리 + 안전교육 신고
Phase 4: SSO 인증 보강 (partner_company_id JWT, 만료일 체크), 권한 테이블 기반 접근 제어

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-12 17:42:59 +09:00
parent a195dd1d50
commit b800792152
63 changed files with 5548 additions and 262 deletions

View File

@@ -61,8 +61,21 @@ const SYSTEM3_PAGES = {
const TKPURCHASE_PAGES = {
'구매 관리': [
{ key: 'purchasing_visit', title: '방문 관리', icon: 'fa-door-open', def: false },
{ key: 'purchasing_partner', title: '협력업체 관리', icon: 'fa-building', def: false },
{ key: 'purchasing_daylabor', title: '일용공 관리', icon: 'fa-hard-hat', def: false },
{ key: 'purchasing_schedule', title: '작업일정 관리', icon: 'fa-calendar-alt', def: false },
{ key: 'purchasing_workreport', title: '업무현황 관리', icon: 'fa-clipboard-list', def: false },
{ key: 'purchasing_accounts', title: '협력업체 계정', icon: 'fa-user-shield', def: false },
],
'협력업체': [
{ key: 'purchasing_partner_portal', title: '협력업체 포털', icon: 'fa-building', def: false },
{ key: 'purchasing_partner_checkin', title: '협력업체 체크인', icon: 'fa-check-circle', def: false },
]
};
const TKSAFETY_PAGES = {
'안전 관리': [
{ key: 'safety_visit', title: '방문 관리', icon: 'fa-door-open', def: false },
{ key: 'safety_education', title: '안전교육 관리', icon: 'fa-graduation-cap', def: false },
]
};
@@ -191,7 +204,7 @@ document.getElementById('permissionUserSelect').addEventListener('change', async
async function loadUserPermissions(userId) {
currentPermissions = {};
currentPermSources = {};
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES };
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES };
Object.values(allDefs).flat().forEach(p => { currentPermissions[p.key] = p.def; currentPermSources[p.key] = 'default'; });
try {
const result = await api(`/permissions/users/${userId}/effective-permissions`);
@@ -208,6 +221,7 @@ function renderPermissionGrid() {
renderSystemPerms('s1-perms', SYSTEM1_PAGES, 'blue');
renderSystemPerms('s3-perms', SYSTEM3_PAGES, 'purple');
renderSystemPerms('tkpurchase-perms', TKPURCHASE_PAGES, 'green');
renderSystemPerms('tksafety-perms', TKSAFETY_PAGES, 'orange');
}
function sourceLabel(src) {
@@ -303,7 +317,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()];
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat()];
const permissions = allPages.map(p => {
const cb = document.getElementById('perm_' + p.key);
return { page_name: p.key, can_access: cb ? cb.checked : false };
@@ -351,7 +365,7 @@ document.addEventListener('DOMContentLoaded', () => {
async function loadDeptPermissions(deptId) {
deptPermissions = {};
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES };
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES };
Object.values(allDefs).flat().forEach(p => { deptPermissions[p.key] = p.def; });
try {
const result = await api(`/permissions/departments/${deptId}/permissions`);
@@ -363,6 +377,7 @@ function renderDeptPermissionGrid() {
renderDeptSystemPerms('dept-s1-perms', SYSTEM1_PAGES, 'blue');
renderDeptSystemPerms('dept-s3-perms', SYSTEM3_PAGES, 'purple');
renderDeptSystemPerms('dept-tkpurchase-perms', TKPURCHASE_PAGES, 'green');
renderDeptSystemPerms('dept-tksafety-perms', TKSAFETY_PAGES, 'orange');
}
function renderDeptSystemPerms(containerId, pageDef, color) {
@@ -441,7 +456,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()];
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_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 };