diff --git a/system1-factory/web/js/vacation-allocation.js b/system1-factory/web/js/vacation-allocation.js index 6bfe907..06e81c1 100644 --- a/system1-factory/web/js/vacation-allocation.js +++ b/system1-factory/web/js/vacation-allocation.js @@ -77,13 +77,25 @@ async function loadWorkers() { return; } - // 개별 입력 탭 - 작업자 셀렉트 박스 + // 개별 입력 탭 - 작업자 셀렉트 박스 (부서별 그룹) const selectWorker = document.getElementById('individualWorker'); + const byDept = {}; workers.forEach(worker => { - const option = document.createElement('option'); - option.value = worker.user_id; - option.textContent = `${worker.worker_name} (${worker.employment_status === 'employed' ? '재직' : '퇴사'})`; - selectWorker.appendChild(option); + const dept = worker.department_name || '부서 미지정'; + if (!byDept[dept]) byDept[dept] = []; + byDept[dept].push(worker); + }); + + Object.keys(byDept).sort().forEach(dept => { + const group = document.createElement('optgroup'); + group.label = dept; + byDept[dept].forEach(worker => { + const option = document.createElement('option'); + option.value = worker.user_id; + option.textContent = `${worker.worker_name} (${worker.employment_status === 'employed' ? '재직' : '퇴사'})`; + group.appendChild(option); + }); + selectWorker.appendChild(group); }); console.log(`Loaded ${workers.length} workers successfully`); } catch (error) { diff --git a/user-management/api/controllers/departmentController.js b/user-management/api/controllers/departmentController.js index ebe7ad5..4471acb 100644 --- a/user-management/api/controllers/departmentController.js +++ b/user-management/api/controllers/departmentController.js @@ -56,8 +56,8 @@ async function update(req, res, next) { async function remove(req, res, next) { try { const id = parseInt(req.params.id); - await departmentModel.deactivate(id); - res.json({ success: true, message: '부서가 비활성화되었습니다' }); + await departmentModel.remove(id); + res.json({ success: true, message: '부서가 삭제되었습니다' }); } catch (err) { next(err); } diff --git a/user-management/api/models/departmentModel.js b/user-management/api/models/departmentModel.js index 40c2a5b..32e3486 100644 --- a/user-management/api/models/departmentModel.js +++ b/user-management/api/models/departmentModel.js @@ -9,9 +9,8 @@ const { getPool } = require('./userModel'); async function getAll() { const db = getPool(); const [rows] = await db.query( - `SELECT d.*, p.department_name AS parent_name + `SELECT d.* FROM departments d - LEFT JOIN departments p ON d.parent_id = p.department_id ORDER BY d.display_order ASC, d.department_id ASC` ); return rows; @@ -20,21 +19,20 @@ async function getAll() { async function getById(id) { const db = getPool(); const [rows] = await db.query( - `SELECT d.*, p.department_name AS parent_name + `SELECT d.* FROM departments d - LEFT JOIN departments p ON d.parent_id = p.department_id WHERE d.department_id = ?`, [id] ); return rows[0] || null; } -async function create({ department_name, parent_id, description, display_order }) { +async function create({ department_name, description, display_order }) { const db = getPool(); const [result] = await db.query( - `INSERT INTO departments (department_name, parent_id, description, display_order) - VALUES (?, ?, ?, ?)`, - [department_name, parent_id || null, description || null, display_order || 0] + `INSERT INTO departments (department_name, description, display_order) + VALUES (?, ?, ?)`, + [department_name, description || null, display_order || 0] ); return getById(result.insertId); } @@ -45,9 +43,7 @@ async function update(id, data) { const values = []; if (data.department_name !== undefined) { fields.push('department_name = ?'); values.push(data.department_name); } - if (data.parent_id !== undefined) { fields.push('parent_id = ?'); values.push(data.parent_id || null); } if (data.description !== undefined) { fields.push('description = ?'); values.push(data.description || null); } - if (data.is_active !== undefined) { fields.push('is_active = ?'); values.push(data.is_active); } if (data.display_order !== undefined) { fields.push('display_order = ?'); values.push(data.display_order); } if (fields.length === 0) return getById(id); @@ -60,12 +56,20 @@ async function update(id, data) { return getById(id); } -async function deactivate(id) { +async function remove(id) { const db = getPool(); - await db.query( - 'UPDATE departments SET is_active = FALSE WHERE department_id = ?', - [id] - ); + const conn = await db.getConnection(); + try { + await conn.beginTransaction(); + await conn.query('UPDATE users SET department_id = NULL WHERE department_id = ?', [id]); + await conn.query('DELETE FROM departments WHERE department_id = ?', [id]); + await conn.commit(); + } catch (e) { + await conn.rollback(); + throw e; + } finally { + conn.release(); + } } -module.exports = { getAll, getById, create, update, deactivate }; +module.exports = { getAll, getById, create, update, remove }; diff --git a/user-management/web/index.html b/user-management/web/index.html index f30c8e1..e2cc492 100644 --- a/user-management/web/index.html +++ b/user-management/web/index.html @@ -465,12 +465,6 @@ -
- - -
@@ -1087,28 +1081,13 @@
-
- - -
-
-
- - -
-
- - -
+
+ +
diff --git a/user-management/web/static/js/tkuser-departments.js b/user-management/web/static/js/tkuser-departments.js index cef1c76..fb4b6e4 100644 --- a/user-management/web/static/js/tkuser-departments.js +++ b/user-management/web/static/js/tkuser-departments.js @@ -5,25 +5,12 @@ async function loadDepartments() { try { const r = await api('/departments'); departments = r.data || r; departmentsLoaded = true; - populateParentDeptSelects(); displayDepartments(); } catch (err) { document.getElementById('departmentList').innerHTML = `

${err.message}

`; } } -function populateParentDeptSelects() { - ['newDeptParent','editDeptParent'].forEach(id => { - const sel = document.getElementById(id); if (!sel) return; - const val = sel.value; - sel.innerHTML = ''; - departments.filter(d => d.is_active !== 0 && d.is_active !== false).forEach(d => { - const o = document.createElement('option'); o.value = d.department_id; o.textContent = d.department_name; sel.appendChild(o); - }); - sel.value = val; - }); -} - let selectedDeptForMembers = null; function displayDepartments() { @@ -34,14 +21,12 @@ function displayDepartments() {
${d.department_name}
- ${d.parent_name ? `상위: ${d.parent_name}` : '최상위'} 순서: ${d.display_order || 0} - ${d.is_active === 0 || d.is_active === false ? '비활성' : '활성'}
- ${d.is_active !== 0 && d.is_active !== false ? `` : ''} +
`).join(''); } @@ -96,7 +81,6 @@ document.getElementById('addDepartmentForm').addEventListener('submit', async e try { await api('/departments', { method: 'POST', body: JSON.stringify({ department_name: document.getElementById('newDeptName').value.trim(), - parent_id: document.getElementById('newDeptParent').value ? parseInt(document.getElementById('newDeptParent').value) : null, description: document.getElementById('newDeptDescription').value.trim() || null, display_order: parseInt(document.getElementById('newDeptOrder').value) || 0 })}); @@ -110,9 +94,6 @@ function editDepartment(id) { document.getElementById('editDeptName').value = d.department_name; document.getElementById('editDeptDescription').value = d.description || ''; document.getElementById('editDeptOrder').value = d.display_order || 0; - document.getElementById('editDeptActive').value = (d.is_active === 0 || d.is_active === false) ? '0' : '1'; - populateParentDeptSelects(); - document.getElementById('editDeptParent').value = d.parent_id || ''; document.getElementById('editDepartmentModal').classList.remove('hidden'); } function closeDepartmentModal() { document.getElementById('editDepartmentModal').classList.add('hidden'); } @@ -122,17 +103,15 @@ document.getElementById('editDepartmentForm').addEventListener('submit', async e try { await api(`/departments/${document.getElementById('editDeptId').value}`, { method: 'PUT', body: JSON.stringify({ department_name: document.getElementById('editDeptName').value.trim(), - parent_id: document.getElementById('editDeptParent').value ? parseInt(document.getElementById('editDeptParent').value) : null, description: document.getElementById('editDeptDescription').value.trim() || null, - display_order: parseInt(document.getElementById('editDeptOrder').value) || 0, - is_active: document.getElementById('editDeptActive').value === '1' + display_order: parseInt(document.getElementById('editDeptOrder').value) || 0 })}); showToast('수정되었습니다.'); closeDepartmentModal(); await loadDepartments(); await loadDepartmentsForSelect(); } catch(e) { showToast(e.message, 'error'); } }); -async function deactivateDepartment(id, name) { - if (!confirm(`"${name}" 부서를 비활성화?`)) return; - try { await api(`/departments/${id}`, { method: 'DELETE' }); showToast('부서 비활성화 완료'); await loadDepartments(); } catch(e) { showToast(e.message, 'error'); } +async function deleteDepartment(id, name) { + if (!confirm(`"${name}" 부서를 삭제하시겠습니까? 소속 인원은 부서 미지정으로 변경됩니다.`)) return; + try { await api(`/departments/${id}`, { method: 'DELETE' }); showToast('부서가 삭제되었습니다'); await loadDepartments(); } catch(e) { showToast(e.message, 'error'); } }