import { Response } from "express"; import { logger } from "../utils/logger"; import { AuthenticatedRequest } from "../types/auth"; import { ApiResponse } from "../types/common"; import { query, queryOne } from "../database/db"; /** * 부서 목록 조회 (회사별) */ export async function getDepartments(req: AuthenticatedRequest, res: Response): Promise { try { const { companyCode } = req.params; const userCompanyCode = req.user?.companyCode; logger.info("부서 목록 조회", { companyCode, userCompanyCode }); // 최고 관리자가 아니면 자신의 회사만 조회 가능 if (userCompanyCode !== "*" && userCompanyCode !== companyCode) { res.status(403).json({ success: false, message: "해당 회사의 부서를 조회할 권한이 없습니다.", }); return; } // 부서 목록 조회 (부서원 수 포함) const departments = await query(` SELECT d.dept_code, d.dept_name, d.company_code, d.parent_dept_code, COUNT(DISTINCT ud.user_id) as member_count FROM dept_info d LEFT JOIN user_dept ud ON d.dept_code = ud.dept_code WHERE d.company_code = $1 GROUP BY d.dept_code, d.dept_name, d.company_code, d.parent_dept_code ORDER BY d.dept_name `, [companyCode]); // 응답 형식 변환 const formattedDepartments = departments.map((dept) => ({ dept_code: dept.dept_code, dept_name: dept.dept_name, company_code: dept.company_code, parent_dept_code: dept.parent_dept_code, memberCount: parseInt(dept.member_count || "0"), })); res.status(200).json({ success: true, data: formattedDepartments, }); } catch (error) { logger.error("부서 목록 조회 실패", error); res.status(500).json({ success: false, message: "부서 목록 조회 중 오류가 발생했습니다.", }); } } /** * 부서 상세 조회 */ export async function getDepartment(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode } = req.params; const department = await queryOne(` SELECT dept_code, dept_name, company_code, parent_dept_code FROM dept_info WHERE dept_code = $1 `, [deptCode]); if (!department) { res.status(404).json({ success: false, message: "부서를 찾을 수 없습니다.", }); return; } res.status(200).json({ success: true, data: department, }); } catch (error) { logger.error("부서 상세 조회 실패", error); res.status(500).json({ success: false, message: "부서 조회 중 오류가 발생했습니다.", }); } } /** * 부서 생성 */ export async function createDepartment(req: AuthenticatedRequest, res: Response): Promise { try { const { companyCode } = req.params; const { dept_name, parent_dept_code } = req.body; if (!dept_name || !dept_name.trim()) { res.status(400).json({ success: false, message: "부서명을 입력해주세요.", }); return; } // 같은 회사 내 중복 부서명 확인 const duplicate = await queryOne(` SELECT dept_code, dept_name FROM dept_info WHERE company_code = $1 AND dept_name = $2 `, [companyCode, dept_name.trim()]); if (duplicate) { res.status(409).json({ success: false, message: `"${dept_name}" 부서가 이미 존재합니다.`, isDuplicate: true, }); return; } // 회사 이름 조회 const company = await queryOne(` SELECT company_name FROM company_mng WHERE company_code = $1 `, [companyCode]); const companyName = company?.company_name || companyCode; // 부서 코드 생성 (전역 카운트: DEPT_1, DEPT_2, ...) const codeResult = await queryOne(` SELECT COALESCE(MAX(CAST(SUBSTRING(dept_code FROM 6) AS INTEGER)), 0) + 1 as next_number FROM dept_info WHERE dept_code ~ '^DEPT_[0-9]+$' `); const nextNumber = codeResult?.next_number || 1; const deptCode = `DEPT_${nextNumber}`; // 부서 생성 const result = await query(` INSERT INTO dept_info ( dept_code, dept_name, company_code, company_name, parent_dept_code, status, regdate ) VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING * `, [ deptCode, dept_name.trim(), companyCode, companyName, parent_dept_code || null, 'active', ]); logger.info("부서 생성 성공", { deptCode, dept_name }); res.status(201).json({ success: true, message: "부서가 생성되었습니다.", data: result[0], }); } catch (error) { logger.error("부서 생성 실패", error); res.status(500).json({ success: false, message: "부서 생성 중 오류가 발생했습니다.", }); } } /** * 부서 수정 */ export async function updateDepartment(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode } = req.params; const { dept_name, parent_dept_code } = req.body; if (!dept_name || !dept_name.trim()) { res.status(400).json({ success: false, message: "부서명을 입력해주세요.", }); return; } const result = await query(` UPDATE dept_info SET dept_name = $1, parent_dept_code = $2 WHERE dept_code = $3 RETURNING * `, [dept_name.trim(), parent_dept_code || null, deptCode]); if (result.length === 0) { res.status(404).json({ success: false, message: "부서를 찾을 수 없습니다.", }); return; } logger.info("부서 수정 성공", { deptCode }); res.status(200).json({ success: true, message: "부서가 수정되었습니다.", data: result[0], }); } catch (error) { logger.error("부서 수정 실패", error); res.status(500).json({ success: false, message: "부서 수정 중 오류가 발생했습니다.", }); } } /** * 부서 삭제 */ export async function deleteDepartment(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode } = req.params; // 하위 부서 확인 const hasChildren = await queryOne(` SELECT COUNT(*) as count FROM dept_info WHERE parent_dept_code = $1 `, [deptCode]); if (parseInt(hasChildren?.count || "0") > 0) { res.status(400).json({ success: false, message: "하위 부서가 있는 부서는 삭제할 수 없습니다. 먼저 하위 부서를 삭제해주세요.", }); return; } // 부서원 삭제 (부서 삭제 전에 먼저 삭제) const deletedMembers = await query(` DELETE FROM user_dept WHERE dept_code = $1 RETURNING user_id `, [deptCode]); const memberCount = deletedMembers.length; // 부서 삭제 const result = await query(` DELETE FROM dept_info WHERE dept_code = $1 RETURNING dept_code, dept_name `, [deptCode]); if (result.length === 0) { res.status(404).json({ success: false, message: "부서를 찾을 수 없습니다.", }); return; } logger.info("부서 삭제 성공", { deptCode, deptName: result[0].dept_name, deletedMemberCount: memberCount }); res.status(200).json({ success: true, message: memberCount > 0 ? `부서가 삭제되었습니다. (부서원 ${memberCount}명 제외됨)` : "부서가 삭제되었습니다.", }); } catch (error) { logger.error("부서 삭제 실패", error); res.status(500).json({ success: false, message: "부서 삭제 중 오류가 발생했습니다.", }); } } /** * 부서원 목록 조회 */ export async function getDepartmentMembers(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode } = req.params; const members = await query(` SELECT u.user_id, u.user_name, u.email, u.tel as phone, u.cell_phone, u.position_name, ud.dept_code, d.dept_name, ud.is_primary FROM user_dept ud JOIN user_info u ON ud.user_id = u.user_id JOIN dept_info d ON ud.dept_code = d.dept_code WHERE ud.dept_code = $1 ORDER BY ud.is_primary DESC, u.user_name `, [deptCode]); res.status(200).json({ success: true, data: members, }); } catch (error) { logger.error("부서원 목록 조회 실패", error); res.status(500).json({ success: false, message: "부서원 목록 조회 중 오류가 발생했습니다.", }); } } /** * 사용자 검색 (부서원 추가용) */ export async function searchUsers(req: AuthenticatedRequest, res: Response): Promise { try { const { companyCode } = req.params; const { search } = req.query; if (!search || typeof search !== 'string') { res.status(400).json({ success: false, message: "검색어를 입력해주세요.", }); return; } // 사용자 검색 (ID 또는 이름) const users = await query(` SELECT user_id, user_name, email, position_name, company_code FROM user_info WHERE company_code = $1 AND ( user_id ILIKE $2 OR user_name ILIKE $2 ) ORDER BY user_name LIMIT 20 `, [companyCode, `%${search}%`]); res.status(200).json({ success: true, data: users, }); } catch (error) { logger.error("사용자 검색 실패", error); res.status(500).json({ success: false, message: "사용자 검색 중 오류가 발생했습니다.", }); } } /** * 부서원 추가 */ export async function addDepartmentMember(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode } = req.params; const { user_id } = req.body; if (!user_id) { res.status(400).json({ success: false, message: "사용자 ID를 입력해주세요.", }); return; } // 사용자 존재 확인 const user = await queryOne(` SELECT user_id, user_name FROM user_info WHERE user_id = $1 `, [user_id]); if (!user) { res.status(404).json({ success: false, message: "사용자를 찾을 수 없습니다.", }); return; } // 이미 부서원인지 확인 const existing = await queryOne(` SELECT * FROM user_dept WHERE user_id = $1 AND dept_code = $2 `, [user_id, deptCode]); if (existing) { res.status(409).json({ success: false, message: "이미 해당 부서의 부서원입니다.", isDuplicate: true, }); return; } // 주 부서가 있는지 확인 const hasPrimary = await queryOne(` SELECT * FROM user_dept WHERE user_id = $1 AND is_primary = true `, [user_id]); // 부서원 추가 await query(` INSERT INTO user_dept (user_id, dept_code, is_primary, created_at) VALUES ($1, $2, $3, NOW()) `, [user_id, deptCode, !hasPrimary]); logger.info("부서원 추가 성공", { user_id, deptCode }); res.status(201).json({ success: true, message: "부서원이 추가되었습니다.", }); } catch (error) { logger.error("부서원 추가 실패", error); res.status(500).json({ success: false, message: "부서원 추가 중 오류가 발생했습니다.", }); } } /** * 부서원 제거 */ export async function removeDepartmentMember(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode, userId } = req.params; const result = await query(` DELETE FROM user_dept WHERE user_id = $1 AND dept_code = $2 RETURNING * `, [userId, deptCode]); if (result.length === 0) { res.status(404).json({ success: false, message: "해당 부서원을 찾을 수 없습니다.", }); return; } logger.info("부서원 제거 성공", { userId, deptCode }); res.status(200).json({ success: true, message: "부서원이 제거되었습니다.", }); } catch (error) { logger.error("부서원 제거 실패", error); res.status(500).json({ success: false, message: "부서원 제거 중 오류가 발생했습니다.", }); } } /** * 주 부서 설정 */ export async function setPrimaryDepartment(req: AuthenticatedRequest, res: Response): Promise { try { const { deptCode, userId } = req.params; // 다른 부서의 주 부서 해제 await query(` UPDATE user_dept SET is_primary = false WHERE user_id = $1 `, [userId]); // 해당 부서를 주 부서로 설정 await query(` UPDATE user_dept SET is_primary = true WHERE user_id = $1 AND dept_code = $2 `, [userId, deptCode]); logger.info("주 부서 설정 성공", { userId, deptCode }); res.status(200).json({ success: true, message: "주 부서가 설정되었습니다.", }); } catch (error) { logger.error("주 부서 설정 실패", error); res.status(500).json({ success: false, message: "주 부서 설정 중 오류가 발생했습니다.", }); } }